기록하자..
왜 Session 대신 JWT 를 선택했는가 본문
혼자서 프로젝트를 진행하던 중 사용자의 로그인 이후 요청마다 사용자의 검증을 위해 어떤 기술을 사용할까 고민하던 중 Session 대신 JWT를 선택했다.
이유를 설명하기 위해서 먼저 HTTP의 특성을 먼저 얘기하고자 한다.
HTTP - Stateless
HTTP는 클라이언트-서버 구조를 따르는 프로토콜이다. 클라이언트가 서버에 요청을 보내고, 응답을 대기하면 서버가 요청에 대한 결과를 만들어서 응답하는 구조이다. HTTP 또다른 특징은 무상태 프로토콜이라는 것이다. 이는 서버가 클라이언트의 상태를 보존하지 않는다는 말이다. HTTP는 서버가 클라이언트의 요청을 처리하면 연결을 끊어 클라이언트에 대한 이전 정보를 가지고 있지 않게 된다.
이 방식은 서버의 확장성(Scale-out)을 높일 수 있고 불필요한 자원의 낭비를 줄일 수 있다는 장점이 있다. 하지만 로그인의 경우를 생각하면 클라이언트가 매 요청마다 로그인을 해야하는 상황이 발생해 버린다..
이를 보완하기 위해 Cookie와 Session 기술을 사용하게 된다.
Cookie
Session을 설명하기 전에 먼저 Cookie에 대해 알아보자.
사전에 정의된 쿠키의 의미는 다음과 같다.
HTTP 쿠키(HTTP cookie)란 하이퍼 텍스트의 기록서의 일종으로서 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 사용자의 웹 브라우저를 통해 인터넷 사용자의 컴퓨터나 다른 기기에 설치되는 작은 기록 정보 파일을 일컫는다. - 위키피디아
클라이언트가 서버에 요청을 보내게 되면 서버는 Set-Cookie 헤더에 쿠키를 담아 전달해준다. 그러면 클라이언트는 서버에서 받은 쿠키를 저장하고 이후 HTTP 요청시 서버로 해당 쿠키를 담아 전달하게 된다.

이후 요청시 Cookie 헤더에 서버로부터 받은 쿠키를 담아 요청을 보내게 된다.
그런데 요청시 쿠키를 그대로 HTTP 메시지에 담아 보내기 때문에 탈취당한다면 사용자의 정보가 그대로 노출되기 때문에 보안에 민감한 정보는 담지 않는게 좋다.
Session
세션은 쿠키와 달리 사용자의 민감한 정보들을 서버에 저장해두고 서버는 SESSIONID를 Set-Cookie 헤더에 담아 보내게 된다.
| HTTP/ 1.1 200 OK Set-Cookie: dec9m123l21424213jkl4123124j |
이렇게 되면 클라이언트는 SESSIONID를 가지고 있게 되고 이후 요청마다 해당 SESSIONID를 Cookie헤더에 담아 서버에 보내게 된다.

- 장점
- SESSIONID 방식을 사용하고 있기 때문에 해당 ID에 매칭된 회원정보를 바로 확인할 수 있어 편리하다.
- 쿠키가 탈취당하더라도 사용자의 정보가 아닌 무의미한 정보가 들어가 있기 때문에 쿠키보다 안전하다.
- 단점
- 해커가 SESSIONID를 중간에 탈취해 클라이언트인척 위장할 수 있다는 한계점이 존재한다.
- 서버를 증설하게 되면 각 서버마다 존재하는 세션정보가 일치하지 않아 Scale-out에 불리하다.
- 이 경우 공통의 세션 DB를 만들어 관리하는 방법이 있다.
- 서버에서 세션 저장소를 사용하고 있기 때문에 서버에 부하가 간다.
JWT
JWT(Json Web Token)는 서명된 토큰이다. 공개키, 개인키를 쌍으로 사용해서 서버에 저장된 개인키를 통해 토큰을 검증하게 된다. 쿠키/세션 방식과 유사하게 HTTP 헤더 (Authorization)에 토큰을 담아 서버가 클라이언트를 식별할 수 있도록 한다.
JWT의 구조는 다음과 같다.

JWT의 구성요소는 아래 3가지와 같은데, 점(.)으로 구분되어 있다.
- Header
- Payload
- Signature
Header
Header는 토큰 타입과 토큰 생성에 어떤 알고리즘이 사용되었는지 알려준다.

개인키로 HS256 알고리즘이 적용되어 암호화가 되었으며, 토큰의 타입은 JWT라는 것을 알 수 있다.
Payload

Payload는 토큰에 담을 정보를 저장하고 있다. Key-Value 한 쌍의 정보를 Claim 이라 한다.
jwt.io 문서에 따르면 Claim은 registered, public, private 이렇게 3종류가 있다고 한다.
- registered - 필수는 아니지만 미리 정의된 클레임의 집합이다.
- iss(issuer) 토큰 발급자
- exp(Expiration Time) 토큰 만료 시간
- iat(Issued At) 토큰 발급 시간
- aud(Audience) 토큰 대상자
- 위와같이 표준 스펙으로 정의된 Claim 스펙이 존재한다. 위에서 말했듯이 필수는 아니기 때문에 상황에 따라 적절히 사용하면된다.
- public - JWT를 사용하는 사용자들이 마음대로 정의할 수 있다. 하지만 충돌 방지를 위해서 이곳(IANA Json Web Token Registry)에 등록하거나 충돌방지 네임스페이스를 포함하는 URI로 정의해야 한다.
- private - 사용에 동의한 regitered 클레임이나 public 클레임이 아닌 당사자들간에 정보를 공유하기 위해 사용되는 클레임이다.
Signature
Signature는 Header와 Payload를 합치고 서버의 개인키를 이용해서 암호화하여 생성한다.

signature는 개인키로 암호화했기 때문에 다른 클라이언트는 임의로 Signature를 복호화할 수 없다.
JWT를 이용한 인증

토큰이 탈취당할 경우를 고려해 AccessToken을 두고 만료시간을 짧게 가져가며, Refresh Token을 사용한다. 프로젝트에는 RefreshToken 저장소로 Redis를 사용하며 I/O 작업을 최소화하기로 결정했다.
- 장점
- 토큰 자체가 인증된 정보이기 때문에 인증정보에 대한 별도의 저장소가 필요없다.
- 클라이언트의 상태를 서버에 저장하는 세션과 다르게 서버에 저장해 두지 않는다.
- 클라이언트로부터 받은 JWT를 검증만하면 되니까..
- 다양한 플랫폼에서 활용이 가능하다.
- 단점
- JWT의 길이가 길어 정보가 많이 담길수록 네트워크 부하가 커진다.
- 토큰은 만료될 때까지 계속 사용이 가능하기 때문에 토큰을 탈취당하면 대처가 어렵다.
- 이를 위해 토큰의 만료시간을 짧게 가져가고 RefreshToken을 둔다.
결론
세션은 서버에 상태를 저장한다는 점과 요청시 매번 세션 저장소를 조회해야 하는 단점이 있어 JWT 방식을 사용하기로 결정했다.
그런데 Refresh Token을 사용하기 위해 별도의 저장소를 사용한다는 점이 추가적인 I/O 작업을 발생시켜 JWT의 장점을 완벽히 누리지 못하게 된다.. 모든 완벽히 해결해 주는 은탄환은 존재하지 않는 것을 느낀다.
참고자료
https://tecoble.techcourse.co.kr/post/2021-05-22-cookie-session-jwt/
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/