2022년 08월 31일(수)
FIKA API 개발
Test 환경 설정 및 TDD 적용
accessToken == null
로 체크하다 보니, accessToken = ""
인 케이스는 거르지 못한 체 JwtParser로 넘어가면서 에러가 터졌다.java.lang.IllegalArgumentException: JWT String argument cannot be null or empty.
accessToken == null
이 조건을 accessToken.isBlank()
로 변경하여 빈 값에 대한 검증도 이루어지도록 하였다.class Parent {
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
List<Child> childList = new ArrayList;
}
class Child {
@ManyToOne
Parent parent;
EntityManager em = emFactory.createEntityManager();
Parent parent = new Parent();
Child childA = new Child();
Child childB = new Child();
parent.childList.add(childA);
parent.childList.add(childB);
em.persis(parent); //Parent만 영속화
위 경우에 childA
, childB
역시 Parent
에 따라가서 persist(childA)
, persist(childB)
가 이루어진다.
문제는 cascade = CascadeType.ALL
에 ALL 옵션이 저장 뿐만아니라 삭제(Delete)도 포함된다는 것이다. 만약 Child
엔티티와 관계가 있는 다른 엔티티가 있다면, 절대 사용하면 안된다. 관련해서 orphanRemoval = true
옵션도 있다.
Child
를 Parent
만 사용하는 경우에만 사용하자.(단독 주인이라고도 하더라)
그래서 잘 모르고 @OneToMany
에 모두 붙혔는데, 엔티티간의 관계를 고려해서 덜어냈다.
@GetMapping("/{dramaId}")
public ResponseEntity<ApiResponse> getDramaInfo(
@RequestHeader(value = "Access-Token", required = false) String accessToken
)
이 처럼, 드라마 정보를 반환 해주는 API가 개발되었다. 만약, 로그인 한 회원은 회원 정보에 따라 드라마 선호에 따라 추천 정보를 반영해 응답하고, 로그인 하지 않은 사용자의 요청에 대해선 그냥 최신순으로 정보를 반환해준다.
회원 / 비회원에 대해선 드라마를 선택(Select)하는 기준만 다를 뿐, 나가는 데이터 형식은 동일하다.
여기서 문제는 required = false
로 설정하여 Token Interceptor에 걸리지 않게 설정해두었다.
만약 accessToken이 존재하는 경우, Token Interceptor에서 이루어지는 accessToken에 대한 검증(만료 되거나 유효하지 않는 형식 등)을 해야한다.
Controller에서 JwtInterceptor
를 의존하여 토큰과 HttpRequest, response를 모두 보내야 하나..? Controller에서 JwtInterceptor를 의존하는 것도 불편하고, 책임도 너무 늘어나는 것 같아 고민이 되었고, 오픈채팅방에 질문을 하였는데 다음과 같은 대답을 들었다.
Custom Annotation을 만들고,
preHandle메서드 인자타입 3번쨰에 Object타입 변수가 하나 들어오는데 그 변수타입이 HandlerMethod로 캐스팅이 되니 이걸로 검증해보아라
라는 답변을 얻었다.
그렇게 구글링을 통해 인터셉터에 다음과 같은 CustomAnnotation
과 로직을 추가하였다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
//...
if (handlerMethod.getMethodAnnotation(TokenNullable.class) != null
&& (accessToken == null || accessToken.isBlank())) {
return true;
}
//...
}
@TokenNullable
@GetMapping("/{dramaId}")
public ResponseEntity<ApiResponse> getDramaInfo(
@RequestHeader(value = "Access-Token", required = false) String accessToken
)
원하는 대로, CustomAnnotation
인 @TokenNullable
을 해당 메서드에 추가하여 null이거나 isBlank()
인 경우엔 true를, 아니면 동일한 토큰 검증이 이루어지도록 하였다.
궁금한건 아직 Controller에 도달하지도 않았는데, 저 handler가 어떻게 Method를 알고있냐는 거였다.
그렇게 여러 구글링을 하던 중 다음과 같은 그림과 함께 설명을 발견했다.
Interceptor에 도달하기 전에, HandlerMapping으로 method reflection에 대해 알고 있기 때문에 위와 같은 동작이 가능했었다는 것이었다!
Spring Web 동작에 대해서도 많이 공부해야겠다..!
오픈채팅방에 질문했을 때, 다른 대답으로 Spring Security가 있으면, SecurityContextHolder 사용하면 jwt 없어도 인증된 사용자의 요청인지 인증되지 않은 사용자의 요청인지 알 수 있다고 알려주셨다. jwt 없는 인증 방식에 대해서도 찾아보도록 하자.
JPA 강의 추가로 수강하자!!