Skip to content

Latest commit

 

History

History
882 lines (740 loc) · 55.1 KB

코드숨.md

File metadata and controls

882 lines (740 loc) · 55.1 KB

코드숨 1주차 과제 Java로 ToDo REST API만들기

  • SDK 를 사용하여 자바 버전 관리
  • 자바 버전 관리하는게 항상 어렵게 느껴졌는데 SDK를 통해 편하게 느껴졌다
  • 처음으로 gradle을 사용하여 프로젝트 시작
  • ./gradlew run
  • ./gradlew test
  • HttpServer API를 사용하여 로컬 서버 테스트
  • HttpServer API를 처음 접해보았다
  • HttpServer를 사용하는 걸 보고 처음 접하는 API라서 당황했다...
  • 생에 첫 코드리뷰를 종립님에게 받아 감격스럽다..
  • 교육에서 제공하는 참고 링크들은 꼭 진득하게 읽어보자


💡

  • 외부에 객체의 상태를 변경할 방법을 제공하는 것이기 때문에 객체의 상태가 언제 어디서 어떻게 변할지 예측할 수 없다.
  • 복잡한 비즈니스 로직을 제공하는 서비스나 협업하는 상황에서는 setter를 제공하는 객체의 상태 변화에 대한 추적이 더욱 어려워진다.
  • getXXX로 클래스의 필드를 가져오는것이 아니라 클래스에게 비교할 정보를 주고 결과만 반환받게 수정
    • ex) Path.resourceEquals({비교 문자열})
  • HttpMethod enum 추가
    • equal메서드를 추가하여 1번과 같이 비교할 문자열을 enum에서 받아 결과를 반환하는 메서드를 추가
  • Path 클래스에 원본 문자열을 저장하는 필드 추가
    • 리뷰 내용 Path 클래스는 path를 감싸서 특정한 서비스를 제공하는 녀석입니다. 원본을 갖고 있다면 디버깅할 때 유용할 거에요.
  • JSON ↔︎ Task 작업
    • Reflection을 사용하여 Task의 필드에 접근
    • 정규표현식을 사용하여 JSON을 나눠보았다
  • 외부에서 정보를 꺼내쓰기 보다는 객체 내부에서 판단이 가능하다면 내부에서 처리하게 하자
  • AutoCloseable 클래스를 알게되었다
  • HttpMethod , HttpResponse enum에 JavaDoc 주석작성
  • TaskJSON문자열로 변경할 때 변경해주는 메서드는 어디에 작성하면 좋을까?
    • Task model 내부?? , TaskConverter 컨버터안에??
    • Task를 위한 Converter라는 클래스가 따로 존재하니 일단 TaskConveter에 작성
  • 응답 헤더 Content-Type 설정
    • 객체 → JSON을 직접 반환하니 한글이 깨졌다

배운 것

  1. SDK를 사용한 자바 버전 관리
  2. 스프링이 아닌 자바만을 사용하여 REST API를 개발
  3. 모델의 Setter를 제거하고 불변 클래스로 만들기
  4. Exception과 RuntimeException 차이
  5. volatile 키워드
  6. enum
  7. RFC (Request For Comments)
  8. JavaDoc 작성
    • 확실한 문서를 레퍼런스로 남길 것
    • 그냥 개발이 아니라 레퍼런스 문서를 토대로 실천하는 활동이다
  9. 의사결정을 글로 작성하는 연습
  10. Jacskon을 사용할 때는 모델의 기본 생성자가 필요하다
  11. 객체지향적 고민
    • 객체에게 데이터를 요구하지 말고 작업을 요청하라
  12. 테스트 코드 작성
    • @DisplayName에 대한 표현은 분명하게 표현하도록 하자
    • 테스트 코드를 읽을 상대의 가독성을 고려하자
  13. Return Early Pattern
  14. 언제부터 인지 필드에 직접 접근하는 것은 절대 안된다!라고 생각했다
    • final로 선언돼 있다면 getter를 만들지 않고 직접 접근하는것도 괜찮다

느낀 것

과제를 진행하면서 처음부터 좋은 코드를 작성하려 노력했던 것 같다
실제로 좋은 코드도 아니었다

  • 점진적인 개선
    1. 일단 만든다
    2. 일단 되게한다
    3. 리팩토링 한다
    4. 위의 과정을 반복한다

과제를 진행하며 위의 과정을 거치면서 점진적인 개선에 대해 조금 깨달은 것 같다
처음부터 너무 많은 것을 생각할 것이 아니라 위의 과정을 생각하자


교훈

1주차 과제가 끝이 났지만 내가 작성한 코드가 마음에 드는 것은 아니다
고쳐야할 부분도 많지만 항상 점진적인 개선을 의식하자

---- 리뷰 내용 중 ----
그냥 기계를 굴러가게 하기만 하는 것이 아닙니다.
레퍼런스 문서를 토대로 실천하는 활동이기도 하다는 점을 곱씹어보세요.
공식 문서의 힘을 끌어다 내 코드에 연결할 수 있다고 생각해 보세요.

그냥 기계를 굴러가게만 하는 것이 아니라는 것을 기억하자


8월 둘째 주 회고 코드숨 2주차 과제 Spring Web 으로 ToDo REST API 만들기

public class TaskService implements CRUDInterface<TaskDTO , Task>
  • service에서 해당 인터페이스를 구현할 때 타입을 지정해주게 수정
  • DTOmodel이 수정되거나 추가되면 TaskDTO , Task를 감쌀 수 있는 인터페이스나 상속이 추가 되어야 겠지만 일단 수정해 보았다
  • List InterfaceCollection확장하고 있어서 new ArrayList로 공간을 새로 만들지 않고 반환 타입을 Collection<V>로 수정
public List<Task> getAllTask(){
  return new ArrayList<>(tasks.values());
}

// public interface List<E> extends Collection<E>

public Collection<Task> selectAll(){
    return tasks.values();
}
  • ExceptionResolver 복습 및 적용
    • 2주차 과제에서 상황에 맞는 응답 코드를 내려주어야 하는데 지금은 null 체크를 하며 처리해주었다
    • 스프링을 사용하여 처리하기 위해 복습
    • 스프링은 예외가 나면 ExceptionResolver를 뒤져서 해결할 수 있는 예외인지 확인한다
    • 해결이 가능하면 직접 해결하고 , 해결하지 못 하면 WAS까지 올라가서 WAS에서 다시 /error로 내려온다
    • 과제에 @ExceptionHandler 적용
      • 해당 예외 클래스의 자식 클래스까지 잡을 수 있다
      • 다른 컨트롤러에 영향을 주지 않는다
  • Http 네트워크 스터디
    • 6장. HTTP 헤더
  • TDD 읽기

배운 것

  1. 패턴 정리 (팩토리 메소드 , 추상 팩토리)
  2. CORS
  3. Spring @CrossOrigin
  4. POJO , JavaBean , DTO
  5. 공식 문서를 보며 Optional을 간단하게 사용해 보았다
    • 단순 null체크는 Optinal을 굳이 사용하진 말자
  6. Jackson Annotation
  7. docs Spring ResponseEntity
    • ResponseEntity.{status}.build()
    • ResponseEntity.of()
  8. 이번 2주차 과제에서는 interface를 적극적으로 사용하면서 구현과 명세를 나누어 보았다
    • 범용적인 인터페이스 (CRUDInterface)를 만들어 볼려고 시도하였지만 마음에 들지 않는다
  9. ExceptionResolver 복습 및 적용
  10. 그림으로 배우는 HTTP 네트워크 1장 ~ 6장
    • 프록시 , 게이트웨이의 차이
    • 프록시의 종류
    • HTTP 헤더 종류
  11. TDD 1장 ~ 10장

느낀 것

  1. 점진적인 개선은 TDD 책에서도 계속 강조 되었다
  2. Factory를 정리하면서 DDD에 대해 알게되었고 도메인 주도 개발 시작하기책을 구매하였다
    • 구글링해서 조사하기 보다는 책을 한번 읽어보고 싶었다
  3. Jackson Annotation에 대해 이번에 처음 알게 되었는데 굉장히 신기했다
    • 기본 생성자 없이 생성자를 직접 지정해주고 키 필드 이름까지 지정이 가능했다..
  4. 네트워크에 대한 개념이 많이 부족하다
    • 캐시 관련 설정은 굉장히 중요하다!!!
    • HTTP 헤더를 배워도 크게 와닿지 않는다
  5. 과제를 진행하면서 자바가 부족하다는 걸 많이 느낀다
  6. 가능하다면 DTO , model 정보들은 final을 붙이도록 노력하자
  7. 내가 사용하는 메소드의 내부는 자세히 확인하자
// 수정 전
return new ArrayList<>(tasks.values());

// 수정 후
public Collection<Task> selectAll(){
    return tasks.values();
}

교훈

  1. 점진적인 개선 중요하다... 계속 강조된다
  2. 피드백을 받아도 사람마다 느끼는 것이 다 다르다
    • 공부방 회고에서 비행기 조종사 피드백 이야기 ( + 통찰력)
  3. 인터페이스의 모든 메소드에는 주석이 있어야 한다!!!
  4. 이번 주 리뷰의 큰 맥락은 (스스로 느끼기엔) 계층별 클래스의 목적과 책임이라고 생각한다
    • 이 클래스는 어디서 주입받아 사용하는게 맞을까 ?? 어디까지 영향을 줘야할까 ??
    • 스스로 결정하며 개발하면서 맞는지 안맞는지는 확실히 잘 모른다
    • 중요한건 내가 어떤 생각으로 왜 이렇게 작성했는지 생각을 하게 되는 것 같다

코드숨 3주차 과제 Spring Mock MVC , 유닛 테스트 작성하기

Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜(Mock) 객체를 지원하는 테스트 프레임워크이다. 
일반적으로 Spring으로 웹 애플리케이션을 개발하면, 여러 객체들 간의 의존성이 생긴다. 
이러한 의존성은 단위 테스트를 작성을 어렵게 하는데, 이를 해결하기 위해 가짜 객체를 주입시켜주는 Mockito 라이브러리를 활용할 수 있다. 
Mockito를 활용하면 가짜 객체에 원하는 결과를 Stub하여 단위 테스트를 진행할 수 있다. 
**물론 Mock을 하지 않아도 된다면 하지 않는 것이 가장 좋다.**

[출처 MangKyu's Diary](https://mangkyu.tistory.com/145)

------------------------------------------------------------------------

Mock은 껍데기만 있는 객체를 얘기합니다.
인터페이스의 추상메소드가 메소드 바디는 없고 파라미터 타입과 리턴타입만 선언된 것처럼, Mock Bean은 기존에 사용되던 Bean의 껍데기만 가져오고 내부의 구현 부분은 모두 사용자에게 위임한 형태입니다.
즉, 해당 Bean의 어떤 메소드가 어떤 값이 입력 되면 어떤 값이 리턴 되어야 한다는 내용 모두 개발자 필요에 의해서 조작이 가능합니다.

[출처 jojoldu](https://jojoldu.tistory.com/226)
  • 위의 글과 같이 가짜 객체를 주입하여 , stub클래스들만 만들어서 진행한다고 한다
  • 그럼 지금은 Controller , Service , Advice나 구현이 다 된 상태이니 굳이 mockitor를 사용해야할까??라는 의문이 들었다
  • 그러나 MockMvc를 사용하여 등록되는 Controller ← Service , Advice 등록과 같은 주입을 어떻게 해줘야할지 몰라서 고생했지만 cozzin님의 코드를 보았다
@BeforeEach
void setup() {
    final TaskRepository repository = new TaskRepository();
    repositoryCleaner = new TaskRepositoryCleaner(repository);
    controller = new TaskController(new TaskService(repository));
    mockMvc = MockMvcBuilders
            .standaloneSetup(controller)
            .setControllerAdvice(new TaskErrorAdvice())
            .build();
}
@MockBean
private TaskService service;
@MockBean
private TaskErrorAdvice advice;

@BeforeEach
void setUp() {
    controller = new TaskController(service);
    mockMvc = MockMvcBuilders
            .standaloneSetup(controller)
            .setControllerAdvice(advice)
            .build();
}

List<Task> setTask(){
    List<Task> tasks = new ArrayList<>();
    Task task1 = new Task(1L , "TEST1");
    Task task2 = new Task(2L , "TEST2");
    tasks.add(task1);
    tasks.add(task2);
    given(service.getTasks()).willReturn(tasks);
    given(service.getTask(1L)).willReturn(task1);
    given(service.getTask(2L)).willReturn(task2);
    return tasks;
}
  • 다른 @Test@BeforeEach에서 setTask() 메서드를 호출한다
  • 하지만 service는 최초 생성되고나서 계속 유지되기 때문에 각 @Test메소드 별로 격리가 되지 않는다
    • 다른 테스트에서 service에 데이터를 넣었다면 다른 테스트에서도 보인다
  • How to clean up mocks in spring tests when using Mockito ⭐️ 해결~~
import static org.mockito.Mockito.reset;

@BeforeEach
void setUp() {
    reset(service);
    controller = new TaskController(service);
    mockMvc = MockMvcBuilders
            .standaloneSetup(controller)
            .setControllerAdvice(advice)
            .build();
}

배운 것

  1. Junit5 , AssertJ , Mockito
    • MockMvc , @WebMvcTest
    • @MockBean을 reset하는 방법
    • MockMvcBuilders
    • verify
  2. baeldung mockito argument matcher
  3. D-C-I 계층 구조의 테스트 코드 작성
  4. XSS , CSRF
    • 추가로 JWT를 읽어보았다
  5. "도메인 주도 개발 시작하기" 읽는 중

느낀 것

  1. mockito를 사용하게 될 상황을 고민해봤다
    • 이번 주는 간단한 처리이기도 하고 이미 구현이 다 돼있는 상황이라서 "mock을 사용해야 할까?" 고민했다
    • 그래서 Spring DI , mock 둘 다 작성해봤다
    • 객체의 행위를 외부에서 지정해주니 Stub으로 껍데기만 짜고 테스트하기 편했다 (API를 몰라서 고생했지만)
  2. mock관련 리뷰를 통해 given을 언제 써야할지 대략 감은 오지만 복잡한 로직의 테스트 코드를 작성해보지 않아 와닿지 않는다
  3. 네트워크 스터디에서 CSRF 방어하는 방법을 들었다 (OAuth 2.0)
    1. 클라이언트자원에 접근할 서버
    2. 자원에 접근할 서버클라이언트에게 난수를 응답하고 난수를 세션에 저장
    3. 클라이언트자원 소유 서버 (+ 난수)
    4. 자원 소유 서버클라이언트에게 난수 + 코드를 응답
    5. 클라이언트자원에 접근할 서버 난수 + 코드를 전달 (코드가 서로 일치하지 않는다면 거부됨)
    6. 자원에 접근할 서버자원 소유 서버에게 난수 + 코드를 검증받고 접근 토큰을 받는다
  4. JWT를 왜 사용하는지 알게 되었고 Refresh Token의 역할을 알게 되었다

교훈

  1. 호출 ↔︎ 역할
    • 테스트 코드를 작성할 때 호출 부분과 역할 부분을 분리해서 생각하고 작성하자
    • 리뷰의 내용이기도 하고 TDD책 에서도 이렇게 작성했다
    • 이렇게 호출해야지 라고 먼저 작성하고 후에 역할을 작성한다
  2. 테스트 코드 작성 시
    • 논리적인 인과에 집중하자
    • 읽는 사람을 배려하자
    • 단어 선택이나 문맥을 고려하자

코드숨 4주차 과제 TDD로 상품 도메인 구현하기

배운 것

  1. TDD를 통해 4주차 과제 진행
    • 유닛 테스트
    • 통합 테스트 X
    • @DataJpaTest
  2. Spring Data Jpa는 영속성 컨텍스트 flush , merge를 언제 진행할까??
  3. 보기에는 불필요한 널 체크 Objects.requireNonNull()를 왜 사용하는지
  4. 유닛 테스트와 통합 테스트의 개념
  5. 불필요한 테스트 코드는 작성하지 말기
  6. DAO vs Repository Pattern
  7. @Trnsactional(readOnly = true) 이 readOnly 는 꼭 지켜야 하는 명세가 아니다
  8. Robert C. Martin The Clean Code Blog
  • Use Cases : 비즈니스 로직과 연관 , 이 어플리케이션이 어떻게 작동하고 어떻게 사용한다
  • 의존성의 방향
    • Service → Repository ← impl Repository
  1. 도메인 주도 설계에서 Service라는 개념은 도메인 레이어나 DB 레이어나 여러 곳에서 사용된다
  • Service(도메인 객체를 사용하는 곳)는 여러 곳에서 사용되기 때문에 상위 패키지를 application으로 정한다
  • Domain의 Entity와 RDB의 Entity는 다르다
  1. baeldung DAO vs Repository Pattern
  • DAO는 데이터 지속성의 추상화입니다. 그러나 저장소는 개체 모음의 추상화입니다.
  • DAO는 스토리지 시스템에 더 가까운 하위 수준 개념입니다. 그러나 Repository는 더 높은 수준의 개념으로 Domain 객체에 더 가깝습니다.
  • DAO는 데이터 매핑/접근 계층으로 작동하여 못생긴 쿼리를 숨깁니다. 그러나 리포지토리는 도메인과 데이터 액세스 레이어 사이의 레이어로, 데이터를 대조하고 도메인 객체를 준비하는 복잡성을 숨깁니다.
  • DAO는 저장소를 사용하여 구현할 수 없습니다. 그러나 저장소는 기본 저장소에 액세스하기 위해 DAO를 사용할 수 있습니다.

느낀 것

  1. 커스텀 예외를 추가했었는데 해당 예외가 무슨 의미인지 질문을 주셨었다
    • 주석을 달지 않아 무슨 생각으로 작성했었는지 잘 모르겠다..
    • 주석 좀 달자
  2. TDD에 대한 방향을 못 잡았다
    • **Service.save()**메서드 테스트 코드 작성 중에 null객체에 대한 테스트 코드를 작성하고 있었다
    • null을 잡아서 예외를 Controller까지 던져서 잡아야할까?? 잡는다면 Advice에 추가해야할까??
    • 미로에 갇힌 느낌이였다. 진행하면서 "뭐를 해야하지?"라는 생각에 많이 잠겼다
  3. 그래서 ServiceController 테스트 코드를 같이 짤려고 하는데 Controller는 어디까지 신경써야할까??
    1. Controller의 메서드만?
    2. Request , Response , Advice ?
    3. 저걸 다 합치면 통합 테스트가 아닌가?
    4. 일단 Service를 모킹하고 컨트롤러의 메소드에만 집중하자

Q Contoller에서 **mock(Service)**를 해서 테스트 코드를 작성 하면서 Service를 개발하면 이 개발한 코드는 어떻게 입증하나요?

A 각각의 유닛 테스트 (Controller , Service ...)들을 자기의 역할에만 집중해서 테스트 코드를 짜며 개발한다

그 후 통합 테스트 (모킹하지 않고)를 진행하면 된다


  • 부족하다고 느낌
    1. 관심사의 분리에 대한 개념
    2. 도메인에 대한 개념
      • 학년의 도메인에 0학년 , 7학년 정보가 들어올 수 없는 것처럼
    3. 불필요한 테스트 코드 작성
      • 테스트 코드는 명세를 작성하는 것
      • 불필요한 테스트 코드를 작성하면 이걸 읽는 사람들이 해당 테스트를 염려한다
    4. TDD 작성 시 탑 다운 , 바텀 업
      • 컨트롤러 테스트 코드를 작성하기 전에 서비스 테스트 코드를 작성했는데 서비스가 어떻게 쓰여야할지 확실히 정할 수 없다
      • 계층을 넘어다니면서 테스트코드를 작성하자

교훈

  1. 2주차 때 실수했던 것을 또 실수 했다
    • 확인하지 않고 형변환을 쓰거나 new로 만들어버리지 말자
    • IDE의 자동완성을 곧이 곧대로 쓰지말자
  2. TDD가 무슨 개념인지 확실히 몰랐지만 이번 과제를 진행하면서 조금 감은 오는 것 같다
    • 테스트 대상에 집중하자

코드숨 5주차 과제 TDD로 사용자 도메인 구현하기 (+ 유효성 검사)

배운 것

  1. docs oracle Using Bean Validation Constraints
  2. JSON 직렬화 , 역직렬화에서 특정 필드 무시하기
    • 자원 생성 , 수정 각 상황에 맞는 DTO 추가
  3. JavaDoc을 작성하는 방법
    • 자바 빌트인 클래스들을 참조 String.class
    • johngrib JavaDoc 작성하기
      • 목표는 특정 코드 덩어리의 대략적인 역할을 3초 안에 파악할 수 있도록 도와주는 것이다.
      • 메소드가 무엇을 입력받아서 무엇을 리턴하는지를 반드시 설명한다.
      • 구현과 주석이 커플링이 생기지 않도록 한다.
      • 주석 상속 규칙
  4. @AllArgsConstructor, @RequiredArgsConstructor 주의 정리
  5. 모킹을 제거하고 Controller 통합 테스트
  6. Serivce 목적에 맞게 분리 리뷰
    • QueryService
    • CommandService
  7. johngrib SOLID

느낀 것

코드숨 과제를 통해 배우고 정리했던 내용 덕분에 토프링 읽기모임에서 나왔던 내용들이 금방 이해됐다.
JavaDoc , 주석의 필요성을 느꼇다
주석은 구현에 의존되게 작성하지 말자
테스트 코드를 작성할 때 모킹을 쓰지 않는 환경에 적응하자
미리 테스트를 만들어 두는 경우에는 까먹지 않게 일부러 테스트를 실패하게 하자

교훈

간편해서 막 쓰는 어노테이션들을 세심하게 살펴보자
생성자 어노테이션을 막 작성한것이 그 예다
한 번에 알아볼 수 있도록 주석을 작성하려 노력하자
JLS를 애용하자


코드숨 6주차 과제 JWT를 활용한 인증 구현하기

  • johngrib 테스트 코드와 반증가능성에 대한 메모
  • 과제를 진행하면서 이해 안가는 상황이 있다.. 🚩
    • Jpa RepositoryRepository InterfaceCrudRepository Interface를 확장하고 있다
    • Service클래스가 Repository Interface를 주입받고 사용하고 있다
    • Spring Data JPA에서 제공하는 interface를 확장하면 제공되는 쿼리를 쓸 수 있다는 것만 알았는데..
    • 그러면 Jpa Repository를 주입받아서 사용해야 하지 않나..?
    • 어떻게 Repository Interface 쓸 수 있는 것일까?
  • 거스름돈 같은 문제는 그리디로 풀었었는데 동전 2같은 문제는 그리디로 풀 수 없다고 한다. 왜?
    • 화폐의 가치가 배수가 아니라서 그렇다
    • 그리디로 풀면 동전 개수 최소값을 구할 수 없다
    • DP배열을 초기화하는 아이디어가 부족했다
    • 화페의 인덱스에 1을 표시만 하려고 했지 배수로 그 금액을 만들 수 있는 화폐의 개수는 카운트 할려고 하지 않았다...
Arrays.fill(dp , MAX);
dp[0] = 0;

for(int i = 1 ; i <= N ; i++){
    int coin = Integer.parseInt(br.readLine());
    for(int j = coin ; j <= K ; j++){
        dp[j] = Math.min(dp[j] , dp[j - coin] + 1);
    }
}

  1. JJWT 라이브러리를 사용하여 인코딩, 디코딩을 통해 사용자 인증하기
  2. 로그인 실패 시
    • UserNotFoundException
    • WrongPasswordException
  3. 인터셉터를 추가하여 product경로 GET메서드를 제외하고 JWT 인증을 적용 시켰다
  4. @Value("${jwt.secret}") String secret
    • application.yml에 있는 속성 정보 사용하기
  5. Request에서 헤더 Authorization 토큰 값만 꺼내기
  6. 빈 검증 실패 시 에러 메세지 커스텀하기
    1. 위의 방법도 있고 Advice에 예외ConstraintViolationException.class를 정의해서 잡는 방법도 있다
  7. JwtUtil 단위 테스트에서는 디코딩이 잘 되는데 왜 Product 통합 테스트에서는 예외가 발생할까..?
    • 테스트 코드에서 사용한 Secret 과 서버에서 사용한 Secret키가 달랐다....

느낀 것

  1. JWT의 Header , Payload , Signature 에 대해 확실하게 알게 되었다
  2. 이번 주 과제 마지막 리뷰에서 특정 도메인 서비스를 위한 한 가지의 기능만 가진 인터페이스 얘기를 해주셨다
    1. 함수형 인터페이스 얘기도 해주셨는데 자바 8을 겉핥기 식으로 읽어서 다음 과제에서도 이어서 얘기하고 싶다
  3. Java-JWT
  4. mozilla Authorization
  5. JWT RFC7519
  6. baeldung Custom Error Message Handling for REST API
  7. REST-assured

코드숨 7주차 과제 Spring Security 적용하기

  1. Spring Docs MockMvcResultMatchers
    • jsonPath()
    • handler()
  2. Github JsonPath
  3. Spring Security에서 GrantedAuthority vs Role
  4. Wikipedia Bcrypt
  5. MDN CSRF , Wikipedia CSRF
    • 신뢰할 수 있는 사용자를 가장하여 웹사이트에 원치 않는 명령을 보내는 공격

  • 6주차 과제에서 인증을 인터셉터를 사용해서 했는데 이번엔 스프링 시큐리티를 적용했다

  • Authorization Basic , Bearer 정리

  • 스프링 시큐리티 설정 정리

  • 정리를 통해 어떤 프레임워크인지 조금 감이 왔다

    • 해당 과제에서는 메서드 주석 , 내장 표현식을 사용하였다.
    • SecurityContextHolder를 더 이해해야 할 것 같다. 6주차 과제에서 인증을 인터셉터를 사용해서 했는데 이번엔 스프링 시큐리티에서 해보자
  • 스프링 시큐리티 의존성을 추가하면 웹 접속 시 자동으로 로그인 화면이 생성된다

    • 우리는 필요 없으니 제거하자
  • 스프링 시큐리티를 직접 구현하기는 힘드니 제공하고 있는 추상화 클래스(WebSecurityConfigurerAdapter)를 사용하자


  • JWT 인증 필터를 구현하여 스프링 시큐리티에 등록하자
  • 필터를 처음부터 구현하지 않고 BasicAuthenticationFilter를 상속받아 사용했다
    • BasicAuthenticationFilter : HTTP 요청의 Authorization 헤더를 처리하여 SecurityContextHolder에 처리한 결과를 담습니다.
    • AuthenticationManager : AuthenticationManager는 Spring Security의 인증 필터를 정의할 수 있는 API입니다.
  • 필터는 서블릿 단에서 처리되기 때문에 스프링 어드바이스에서 잡을 수 없다.
    • 아래와 같이 authenticationFilter에서 토큰을 분석하는데 해당 필터에서 InvalidTokenException이 발생할 수 있기 때문에 해당 예외를 잡아주는 필터가 더 필요하다.
    • authenticationErrorFilter라는 전처리기 필터를 추가해서 잡아주자.

SecurityJavaConfig.class

@Override
public void configure(HttpSecurity http) throws Exception {
   Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), authenticationService);
   Filter authenticationErrorFilter = new AuthenticationErrorFilter();

   http.csrf().disable()
            .addFilter(authenticationFilter)
            // authenticationFilter를 필터로 등록
            // authenticationFilter의 전처리 필터 authenticationErrorFilter를 등록
            .addFilterBefore(
                authenticationErrorFilter, 
                JwtAuthenticationFilter.class
            );
}

  • 필터에서 토큰을 분석하고 아래와 같이 분석된 정보를 컨트롤러에서도 사용할 수 있게 담아주자
    • 컨트롤러에서는 Authentication 타입을 받아주면 사용할 수 있다

JwtAuthenticationFilter.class

   ...

   SecurityContext context = SecurityContextHolder.getContext();
   context.setAuthentication({Authentication});

   ...

  • @EnableGlobalMethodSecurity
  • 위의 설정을 따로 해주면 403 Forbidden 상태가 반환된다
  • 그럼 이 상태를 그대로 반환할 것인지? 아니면 커스텀할 것인지? 결정하는 것
    • 아래와 같이 Authentication 관련 에러들은 뭐든지 HttpStatus.UNAUTHORIZED 이걸로 바꿀 수 있다

SecurityJavaConfig.class

@Override
public void configure(HttpSecurity http) throws Exception {
   Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), authenticationService);
   Filter authenticationErrorFilter = new AuthenticationErrorFilter();

   http.csrf().disable()
            .addFilter(authenticationFilter)
            .addFilterBefore(authenticationErrorFilter , JwtAuthenticationFilter.class)
            .exceptionHandling()
            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}

  • Wikipedia Bcrypt
    • 암호화된 메시지를 읽을 수 있도록 다시 평문으로 변환하는 것을 복호화라고 부른다.
    • 그런데 복호화를 할 수 없는 암호화가 존재한다.
    • 가장 대표적인 예가 bcrypt다.
    • 사용자의 비밀번호를 데이터베이스에 저장할 때 주로 쓰인다.
    • 한번 암호화된 비밀번호는 복호화 할 수 없어 원본 비밀번호를 알 수 없다.
  • 해시 함수
    • 정해지지 않은 길이의 문자열을 입력받아 고정된 크기의 결괏값을 출력하는 함수이다.
    • 단방향 함수(one-way function) 여야 한다.
    • 즉 메시지 m을 알면 h(m)을 구하기는 쉽지만, h(m)을 을 만족하는 x를 찾는 것은 구할 수 없어야 한다.
    • 어떤 값을 계산할 수 있지만 그 역을 구하는 것은 어려운 함수를 말한다.
  • 충돌 저항성(collision resistance)
    • 충돌이란 두 개의 서로 다른 입력 m1과 m2가 있을 때 h(m1) = h(m2)가 되는 상황을 말한다.
    • 해시 함수의 입력은 무한하므로, 이와 같은 충돌을 수없이 만들 수 있다.
    • 그래서 해시 함수에 완벽한 충돌 회피란 없다.
  • 역상 저항성
    • 해시 함수의 결과를 이미 알고 있다. 그때 이 결과를 만드는 입력값 m을 찾는 것이 얼마나 어려운지를 나타낸다.
  • 제 2 역상 저항성
    • 입력값 m을 이미 알고 있는 상태에서 h(m) = x을 만족하는 x를 얼마나 찾기 쉬운지를 나타낸다.
  • johngrib OAuth
  • buzzvill 멀티리포 vs 모노리포
  • 7주차 과제 풀이
    • user 자신의 정보만 수정 가능하게
    • ROLE 엔티티 추가 후 사용자마다 가지고 있게 변경
    • Spring Security Filter에서 로그인 시 사용자의 ROLE 정보 모두 조회 후 SimpleGrantedAuthority를 추가 해줬다

Spring Security를 배우면서 중요하다고 생각되는 것들

  1. Spring Reference SecurityContextHolder
  2. RFC1945 section11.1 Basic Authentication Scheme
  3. Spring Docs OncePerRequestFilter
  4. Spring Reference Access Control using @PreAuthorize and @PostAuthorize

테스트 코드를 작성하면서 고민했던 것

  • 테스트 코드를 작성할 때 픽스처를 작성하면서 final로 선언할 수 있는 것들은 클래스의 필드메소드 내에서 선언하는 것이 @BeforEach에 작성하는 것보다 좋을까?
  • 종립님 리뷰
    • "저는 final이 많으면 많을수록 좋다고 생각합니다. 한편 테스트에서는 @BeforeEach는 준비 작업을 명시해줄 필요가 있을 때 쓰죠. 용도보다는 가독성을 더 생각하는 쪽이 바람직하다는 관점이라 할 수 있겠어요."
  • jojoldu 테스트 픽스처 올바르게 사용하기
    • 절대 테스트 메소드를 이해하기 위해 다른 부분을 찾아보게 만들어선 안된다.
    • 테스트를 수정해도 다른 테스트에 영향을 주어서는 안되게 하는 것이 좋은 테스트의 기본조건이다.

두 분다 공통적으로 말씀하시는 부분은 **"가독성"**이다.
그리고 격리된 테스트 코드를 짜는게 힘들었다.
항상 ddl-auto: create로 설정했었는데 실무에서는 이렇게 할 수 없을 것이다..

스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다.

내가 작성한 테스트 코드들이 어떤 식으로 실행되는지 이해가 부족한 것 같다


코드숨 8주차 과제 Spring REST Docs 적용, Docker로 빌드 및 배포

Spring REST DocsDocker, Gradle 명령어를 처음 사용해 보았다
Spring REST Docs를 사용하면서 테스트 코드를 짜야하는 이유가 한 개 더 생겼다

  • @Nested를 사용한 계층 테스트 코드를 작성하고 document()를 사용하면 예외가 발생한다.
  • 계층형 내부 클래스에 어노테이션들을 반복적으로 작성해줘야 한다.
  • 이번 과제에서 REST Docs와 관련된 테스트 코드는 given-when-then으로 작성했다.

Docker를 사용하면서 로컬에 깔려있는 디비를 지우고 컨테이너로 사용하는 방법을 배우고 설정 정보도 분리 할 수 있다는 것을 배웠다

  • 토비의 스프링 읽기모임 예제 레포도 토비님이 도커를 넣어놓으신걸 이번 과제를 배우고 깨달았다 ㅎㅎ
  • 따로 강의를 사서 듣고 싶다

특정 도메인 서비스를 위한 한 가지의 기능만 가진 인터페이스

  • 컨트롤러와 서비스를 한 개의 책임만 가지게 세세한게 분리해 보았다
  • 테스트 코드를 짜거나 수정할 때 편리하다는 것을 느꼇다
  • 이렇게 까지 분리할 수 있구나 싶었다

코드숨 끝

벌써 코드숨이 끝났다
개발도 많이 배웠지만 공부하는 방법이나 방향성에 대해서도 많이 배운 것 같다
혼자 TDD와 테스트 코드를 배웠다면 이렇게 못 배웠을것이다..
다른 사람이 보기에는 고작 8주 가지고 뭐 대단하게 바뀌겠냐 라고 말 하겠지만, 이 교육을 듣기 전과 후로 나뉘는 것 같다.

  1. 테스트 코드를 왜 짜는지?
  2. 테스트 주도 개발이 무엇인지?
  3. 통합 테스트와 단위 테스트가 무엇인지?
  4. 생각을 하면서 개발하는 것 (개발에 정답은 없지만 이렇게 작성한 이유를 설명할 수 있게)
  5. 객체 지향 프로그래밍이 무엇인지?
  6. DTO와 불변 클래스를 왜 쓰는지? 등등

정말 소중한 경험이었다.
8주 동안 아낌없이 조언을 해주신 리뷰어분들과 윤석님에게 감사하다.


아래의 3개에서 선택해야 했다

  1. 우테캠프로 사전 과제
    • 사전 과제는 객체지향설계 능력을 많이 보는 과제들이다
  2. 코드숨 포트폴리오반
    • 협업 경험, 데브옵스 경험이 가능하다
  3. 하고 싶은 것
    • 북마크 토이 프로젝트 하기
    • JPA 공부
    • 캐싱 , Redis 사용해보기
    • 스프링 시큐리티 컨텍스 홀더 분석해보기
    • 토프링 읽기 모임, 오브젝트 스터디 진도 따라가기

항상 우테캠을 듣고 싶었어서 사전과제를 선택했다. (시간남으면 하고 싶은 것 하기)
일단 사전 과제에 집중하자

뭔가를 공부할 때 한 개만 선택해서 한 개에만 몰두하는 단점이 있다

  1. 여러 개를 해보면서 버리는 것은 문제가 안된다
  2. 스스로 뭐가 중요한지 고민을 잘 하고 최선의 선택을 하기
  3. 최소의 노력으로 최대의 결과 만들기