개발로그(004)

원태연·2022년 8월 31일
1

개발로그

목록 보기
4/4
post-thumbnail

2022년 08월 31일(수)

✅ 오늘 한 일

FIKA API 개발
Test 환경 설정 및 TDD 적용

👀 발생했던 오류

  1. Http Header에 담기는 로그인 정보(AccessToken)에 대하여 interceptor를 통해 null값을 검증하였다. 근데, accessToken == null로 체크하다 보니, accessToken = ""인 케이스는 거르지 못한 체 JwtParser로 넘어가면서 에러가 터졌다.
    java.lang.IllegalArgumentException: JWT String argument cannot be null or empty.
    해결책 -> accessToken == null 이 조건을 accessToken.isBlank()로 변경하여 빈 값에 대한 검증도 이루어지도록 하였다.

🎓 배운 점

  1. Cascade 영속성 전이에 대한 개념.
    Parent엔티티와 관계가 있는 Child 엔티티가 있을 때, Parent가 Child에 대해 Casacde 옵션을 걸면, 영속성이 전이 된다. Child에 대하여 하나하나의 영속화를 하지 않아도, Parent에 포함된 Child에 대해서 알아서 영속화가 진행된다는 개념이다
    ex)
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 옵션도 있다.

ChildParent만 사용하는 경우에만 사용하자.(단독 주인이라고도 하더라)

그래서 잘 모르고 @OneToMany에 모두 붙혔는데, 엔티티간의 관계를 고려해서 덜어냈다.

  1. Handler Interceptor
    고민은 Access-Token이 nullable한 Contorller 메서드에서 시작 되었다.
	@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 동작에 대해서도 많이 공부해야겠다..!

❗️앞으로 알아보면 좋은 것

  1. 오픈채팅방에 질문했을 때, 다른 대답으로 Spring Security가 있으면, SecurityContextHolder 사용하면 jwt 없어도 인증된 사용자의 요청인지 인증되지 않은 사용자의 요청인지 알 수 있다고 알려주셨다. jwt 없는 인증 방식에 대해서도 찾아보도록 하자.

  2. JPA 강의 추가로 수강하자!!

profile
앞으로 넘어지기

0개의 댓글