참고
https://jwt.io/
https://velopert.com/2389
https://covenant.tistory.com/201
https://www.youtube.com/watch?v=1QiOXWEbqYQ
이전 포스트 https://velog.io/@dev-joon/%EC%9D%B8%EC%A6%9DAuthentication%EA%B3%BC-%EC%9D%B8%EA%B0%80Authorization
에서는 인증과 인가, 세션과 쿠키와 그 장단점들을 살펴보았다. 이번에는 그 단점들의 Stateless 와 Stateful의 충돌을 없애기 위한 방법인 Token 기반 시스템을 살펴보자.
토큰 기반 시스템의 존재이유는 바로 서버의 무상태성(Stateless)를 유지하기 위함이다. 상태정보를 저장하지 않으면, 서버는 클라이언트에서 들어오는 요청만으로 작업을 처리하기에 클라이언트와 서버의 연결고리가 없다. 이는 서버의 확장성(Scalability)를 높이는 효과를 가져온다.
무상태성(Stateless)의 유지와 확장성(Scalability)
토큰을 클라이언트측에서 보관하기 때문에 서버의 입장에서는 무상태성을 유지할 수 있으며, 위에서 언급한 것과 같이 서버를 확장하기가 용이해진다. 또한 세션을 만약 서버측에 저장하고 있고, 서버를 여러대로 분산하여 요청을 처리하고 있다면, 세션DB를 따로 만든다던가 아니면 해당 요청을 세션ID가 위치한 서버로만 보낼 수 있도록 하는 등의 설정이 불필요해진다.
보안
사용자 정보를 클라이언트나 서버나 세션DB에 저장하지 않기 때문에 쿠키의 취약점으로 인해 발생하는 보안적인 문제가 사라진다.
Extensibility(확장성)
이 확장성은 Scalability와는 다른 확장성을 의미한다. Scalability는 서버의 확장이지만, Extensibility는 로그인(인증) 정보가 사용되는 분야의 확장을 의미한다. 토큰을 사용한다면, 다른 서비스에서도 한 서비스에서 설정된 권한을 공유할 수 있다.
OAuth로 예시를 들자면, 요즘 어플에 회원가입을 할 때 구글이나 카카오계정으로 로그인하기 기능같은 것들이 이에 해당한다고 볼 수 있다.
데이터 증가에 따른 네트워크 부하
모든 요청에 대해 토큰이 전송되므로 토큰에 담기는 정보량에 따라 네트워크 부하가 증가한다.
탈취
토큰 자체에 모든 자체적인 정보를 담고 있기에 JWT가 만료시간 전에 탈취당할 시 서버에서 할 수 있는 조치가 없다.
Payload Base64 Encoding
Payload 자체가 암호화 된것이 아닌 Base64로 인코딩된 것이기 때문에 중간에 토큰을 탈취하여 디코딩하면 데이터를 볼 수 있다. 따라서 민감한 데이터는 Payload에 넣으면 안된다.
Stateless
JWT는 상태라는 것이 없기 때문에 한번 만들어지면 임의로 삭제하는 것이 불가능하다. 이를 위해 만료시간은 필수적으로 넣어주어야 한다.
JWT란?
typ: Type of the Token: JWT
alg: Hashing Algorithem (ex. HMAC SHA256, RSA)
토큰을 검증할 때 사용되는 Signature 부분에서 알고리즘이 사용된다.
서비스에 필요한 정보가 아닌, 토큰에 대한 정보를 담기 위해 이름이 이미 정해진 클레임이다.
Registered Claim의 사용은 모두 선택적으로 할 수 있다.
iss
: 토큰 발급자 (issuer)
sub
: 토큰 제목 (subject)
aud
: 토큰 대상자 (audience)
exp
: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어 있어야 한다.
nbf
: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념이다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다.
iat
: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있다..
jti
: JWT의 고유 식별자 - 주로 중복적인 처리를 방지하기 위하여 사용되며, 일회용 토큰(Access Token) 등에 사용된다.
사용자 정의 클레임으로 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URI 포맷으로 사용한다.
등록된 클레임도 아니며, 공개 클래임도 아닌 서버와 클라이언트 사이에 임의로 지정한 정보를 저장하기 위해 만들어진 사용자 지정 클레임이다.
Header와 Payload는 암호화를 한 것이 아닌, 단순히 JSON 문자열을 base64로 인코딩한 것에 불과하기에 누구나 디코딩을 한다면 헤더와 페이로드의 내용을 볼 수 있다.
따라서 해커가 JWT를 탈취하여 수정한 후 서버로 보내는 등의 경우를 방지하기 위해 있는 것이 바로 Signature부분이다.
서명은 헤더의 (인코딩 값 + 정보의 인코딩 값)에 Secret Key로 Hash를 하여 생성한다.
그리고 이 모든 Header.Payload.Signature을 .중간자로 합쳐주면 JWT가 생성이된다.
해커가 토큰을 탈취할 경우, JWT의 토큰 만료시간을 짧게 지정하여 남용을 방지하는 조치를 취할 수 있지만, 이는 본래의 사용자 또한 사용할 수 없게 되는 부작용을 초래한다. 이를 방지하기 위해 만들어진 것이 바로 Refresh Token이라는 개념이다.
1. 유저가 ID와 password를 입력하여 서버에 로그인 인증을 요청한다.
2. 서버는 유저로부터 받은 정보를 확인 후, 헤더와 페이로드를 서버에 저장된 Secret Key를 지정된 알고리즘으로 돌려서 나온 값을 Access Token(JWT)와 Refresh Token로 발급한다.
3. 클라이언트는 JWT가 요구된 API를 요청할 때(장바구니 등등) Authorization header에 Access Token을 담아서 요청한다.
4. 서버는 JWT 토큰의 Signature를 체크하여 이상을 확인한다. 그 후 응답한다.
5. Access Token의 시간이 만료되면 클라이언트는 Refresh Token을 이용하여 새로운 Access Token을 발급받는다.