JWT를 Header에? Body에?

nathan·2022년 6월 26일
4

ETC

목록 보기
1/1
post-custom-banner

안녕하세요
코드스쿼드에서 이번 마지막 프로젝트인 이슈트래커를 하면서 생긴 문제가 있었습니다.
해결방안은 아니지만, 잘못 생각하고 있었던 부분을 되짚어보려 합니다.

틀린 내용이나 이해를 잘못한 부분이 있다면 언제든 댓글로 남겨주시면 감사드리겠습니다 🙏


목차

  • JWT를 Header에 담기
  • 오해 바로잡기! (JWT를 꼭 Header에 담아 넘길 필요는 없다?)
  • JWT를 Body에 넣어서 보내는 사례

상황

  • Github OAuth 로그인 구현 중 백엔드 서버에서 생성한 JWT를 어떻게 넘겨줄 것인지 고민하고 있는 상황입니다.

JWT를 Header에 담기

  • JWT가 탈취 위험이 있으므로 헤더에 쿠키를 통해 보내야한다고 알고 있었습니다.
  • 따라서 JWT를 생성하고, Set-Cookie를 통해 Header에 담아서 Response를 보내주었습니다.
  • 이 때 다음의 문제가 발생하였습니다.
  • 다음 그림을 통해 Response Header와 Cookies에는 잘 담기는 것을 확인할 수 있었습니다.

  • 그러나, Application > Storage 에는 Cookie가 다음과 같이 담겨야 하지만, 클라이언트 측에서 저장이 안된다는 말 밖에 돌아오지 않았습니다..

  • 로컬에서는 다음과 같이 잘 저장되었습니다. 그래서 저는 여기서 CORS 문제일 것이라는 추측을 하게 됩니다..

이를 해결하기 위해 시도한 방법들

  1. WebConfig에 allowCredential 설정 및 allowHeaders 추가

    • addCorsMapping을 담당하는 CorsConfiguration에 대해서 공식문서에 다음과 같이 나와있습니다.

    By default a newly created CorsConfiguration does not permit any cross-origin requests and must be configured explicitly to indicate what should be allowed.

    • 따라서 Credential과 Headers를 allow 해주면 문제가 해결될 것 같은 느낌이 들어 corsMapping에서 다음과 같이 설정을 추가하였습니다.
    	@Override
    		public void addCorsMappings(CorsRegistry registry) {
    
    			registry.addMapping("/**")
    				.allowedOrigins("http://localhost:3000")
    				.allowCredentials(true)
             .allowedHeaders("*")
    				.allowedMethods("GET", "POST", "PUT", "PATCH");
    		}
    • 결과 : 실패
  2. ResponseCookieSameSiteSecure 설정을 해주기

    • 구글 크롬의 경우 새로운 쿠키 정책의 도입 이후samesite의 값이 Lax로 디폴트 값을 갖습니다.
    • SameSite란, 서로 다른 도메인 간 쿠키 전송에 대한 보안을 설정하는 속성입니다.
      • 속성 값의 종류로는 Strict, Lax, None이 있습니다. (None쪽으로 갈수록 보안의 벽이 낮아짐)
    • SameSite 값을 None으로 설정할 경우 모든 도메인에서 쿠키를 전송 및 사용이 가능해집니다. (단, 이 방법으로 인해 CSRF 및 각종 보안 위협에 취약해질 수 있습니다.)
    • 다음과 같은 방법으로 samesite를 커스텀할 수 있습니다.
    		private String getCookie(User user, TokenType type) {
    			String token = getToken(user, type);
    			return ResponseCookie
    				.from(type.getType(), token)
    				.maxAge(type.getTime())
    				.sameSite(SameSite.NONE.toString())
    				.secure(true)
    				.path("/")
    				.build()
    				.toString();   
    • 단, SameSite=None의 경우 꼭 추가적으로 Secure 옵션을 true로 해주어야 모든 도메인간 쿠키 전송 및 이용이 가능해집니다. 만약 Secure 설정을 하지 않았을 경우 브라우저는 경고메시지와 함께 쿠키 적용을 막습니다.

    • 결과 : HTTPS 적용을 해 볼 시간이 없어 pass

다음 두 가지 방법 외에도 각종 자잘한 설정을 고쳐보고 시도했지만, 결국 쿠키는 저장되지 않았습니다. 따라서 우리 팀은 이를 ResponseBody에 넣기로 결정합니다. ㅠㅠ

  • Header에 쿠키를 넣어 보내는 것이 정석이라고 알고 있었기 때문에, 이 때 느꼈던 감정은, 결국 해결책을 찾지 못했다는 아쉬움과 허탈함 그리고 Body에 넣어서 보내야하는 찝찝함이었습니다.

...

그러나

...

그럴 필요가 없었다..!!??

오해와 진실

  • 사실 JWT를 쿠키에 담아 헤더로 통신한다는 것은 백엔드 -> 클라이언트 측 통신보다는 클라이언트 -> 백엔드 를 염두에 두고 있는 말이었습니다.
  • 따라서 현재 헤더에 쿠키로 담아 전송하고자 했던 것은 굳이 그럴 필요가 없었습니다.
  • 즉, 클라이언트 측에서 ResponseBody로 받은 JWT를 쿠키에 담아 보관하고, Request Header에 AUTHORIZATION: Bearer [JWT]와 같은 방식으로 Request를 보내면 됩니다.
  • 또한 RFC의 부록 B에 언급된 바와 같이 JWT는 헤더나 바디에 담아도 될 만큼 충분히 작은 크기라고 말하고 있습니다.

    JWTs are intended to provide a simple security token format that is
    small enough to fit into HTTP headers and query arguments in URIs.
    참고 : https://datatracker.ietf.org/doc/html/rfc7519

JWT를 Body에 넣어서 보내는 사례

  • 실제로 KAKAO 로그인의 경우 JWT를 ResponseBody에 넣어 보내는 것을 Sample 코드로 제공하고 있습니다.


이번 사례를 통하여, JWT는 항상 헤더에 담겨 통신되어야 한다는 오해를 바로 잡을 수 있었습니다.
물론, 이번에는 헤더에 담아 통신하는 것이 실패하게 되었으나, 가능은 한 것으로 알고있기 때문에 추후 기회가 된다면, 도전해보는 것도 나름 의미가 있을 것으로 생각됩니다.

긴 글 읽어주셔서 감사합니다.

profile
나는 날마다 모든 면에서 점점 더 나아지고 있다.
post-custom-banner

5개의 댓글

comment-user-thumbnail
2022년 6월 27일

안녕하세요 선생님 그래서 다과는 왜 안담겼나요

1개의 답글
comment-user-thumbnail
2022년 6월 28일

엇 저는 그냥 처음에 "바디가 편하니까 바디에 보내야지~" 하고 DTO 만들어서 보냈는데... 꽤 깊은 고민이 필요한 주제였군요... 나단 글 읽고 자료를 좀 찾다 보니 뭔가 HTTP를 조금 더 제대로 깊게 공부해 봐야겠다는 생각이 드네요. 덕분에 또 좋은 공부거리를 찾았네요 흐흐 잘 읽었습니다!

1개의 답글