1. 토큰
사용자는 리소스에 접근하기 위한 인증 프로세스의 결과로 토큰을 얻는다. 토큰은 요청자가 리소스 접근을 허용할 만한 사람인지 확인(인증)하기 위해 요청과 함께 제공되어야 한다.
웹 앱에서는 엔드포인트가 리소스가 되고, 토큰은 일반적으로는 클라이언트가 HTTP 헤더를 통해 보내는 문자열이 된다. 이 문자열은 UUID가 될 수도, JWT가 될 수도 있다.
그렇다면 토큰을 쓰는 이유는 무엇일까? 토큰을 사용함으로써 얻을 수 있는 장점에는 다음과 같은 것들이 있다.
리소스를 요청할 때마다 자격 증명을 공유할 필요가 없다.
- 스프링 시큐리티 시리즈 (1)에서와 같은 경우, 인증이 필요한 요청마다 헤더에 Authorization : Basic …으로 username과 password를 인코딩해서 보내야 한다.
- 위 경우 자격 증명이 지속적으로 노출되고, 가로채질 위험도 커진다.
- 토큰을 이용하면 처음에만(로그인) 자격 증명을 보내고, 이후에는 인증 프로세스의 결과로 받은 토큰의 리소스 접근 권한을 이용할 수 있다.
토큰의 수명을 지정할 수 있다.
- 한 번 토큰이 가로채지더라도 그 토큰을 계속해서 사용할 수는 없게 할 수 있다.
- 토큰 노출이 발견되면 해당 토큰을 거부할 수도 있다.
- 자격 증명을 무효로 하지 않고 토큰 무효로 리소스에 대한 접근을 제어할 수 있다.
클라이언트가 요청 시 보내야 하는 권한 등의 세부 정보를 토큰에 저장할 수도 있다.
- 서버에서 세션을 관리하지 않고, 세부 정보를 담은 토큰을 통해 클라이언트에서 관리하게 할 수 있다.
- 서버를 Stateless하게 만들어 부담을 줄일 수 있다.
- 인증 책임을 시스템의 다른 구성 요소에 위임할 수 있다.
- 다른 플랫폼에서 발급 받은 토큰을 이용해, 인증 과정을 대신하게 할 수 있다.
2. JWT(JSON Web Token)
토큰은 서버 측에서 사용자를 식별할 수 있다면 무엇이든 될 수 있다. JWT는 IEFTF(Internet Engineering Task Force)에서 만든 토큰의 표준 구현 중 하나로 다음과 같은 구성으로 이루어져 있다.
2.1. JWT의 구성
JWT는 헤더, 페이로드, 시그니처의 세 부분으로 구성되며 각 부분은 마침표로 구분된다. 헤더와 페이로드는 JSON으로 형식이 지정되고 Base64로 인코딩된다. 아래는 jwt.io에서 주어지는 샘플이다.
(1) 헤더
헤더에서는 토큰의 타입과 시그니처의 해시 함수 종류를 지정한다. (각각 예시의 typ
와 alg
)
(2) 페이로드
페이로드에는 권한 부여 등에 필요한 세부 정보들을 저장한다. 이 페이로드에는 여러 종류의 클레임들이 포함되어 있다.
-
Registered Claims
- 토큰 자체에 대한 정보들을 담기 위해 표준으로 정해진 클레임.
- 사용이 필수인 것은 아니지만, 서드파티 앱과의 interoperability를 위해 사용하는 것이 권장됨
iss
(issuer): JWT 발급자
sub
(subject): JWT 제목(흔히 이메일 사용)
aud
(audience): JWT 대상자
exp
(expiration time): 만료 시간
nbf
(not before time): JWT가 활성화되는 시간(이 시간이 지나기 전에는 토큰 처리가 되지 않음)
iat
(issued at time): 토큰 발급 시간
jti
(JWT ID): JWT의 고유 식별자. 토큰을 단 한 번만 사용하게 하기 위해 사용
-
Custom Claims
- 위와 달리 임의로 클레임을 지정해서 사용할 수도 있다.
- Public
- Private
(3) 시그니처
토큰 인코딩 및 유효성 검증에 사용하는 디지털 서명이다. 헤더와 페이로드를 Base64 인코딩하고, 지정한 비밀 키로 헤더에서 정의한 알고리즘을 이용해 해싱한 후 다시 Base64로 인코딩해서 만든다.
서명을 이용하면 JWT가 중간에 가로채져 내용이 변경되거나 하지는 않았는지 확인할 수 있다. (위 캡처의 VERIFY SIGNATURE를 참고)
2.2. JWT의 특징
(1) 장점
- Stateless하게 유지할 수 있다.
- 서버에서 사용자 요청이 유효한지 확인하기 위해 세션을 만들어 유지할 필요가 없다.
- 서버의 부담이 줄어듦
- 작다
- Base64로 인코딩 되어 있고, URL, HTTP 헤더, HTTP POST 파라미터 등으로 쉽게 전달된다
- 암호 서명
- JWT를 발급하는 인증 서버에 의해 서명되어 있다.
- 중간에 가로채져 변형되는 경우를 체크할 수 있다.
- Self-contained
- JWT는 암호 서명되어 있기에 이를 받는 서비스는 토큰의 내용이 유효한지를 확인할 수 있다.
- Payload에 인증에 필요한 정보들을 담고 있기 때문에 이를 가지고 다시 인증 서버에 유저 정보를 요청할 필요가 없다.
- 시간과 자원이 많이 드는 DB 조회 과정을 생략할 수 있다
- 확장성
- 인증 서비스에서는 토큰을 발급할 때 여러 필요한 추가 정보들을 넣을 수 있다.
- 토큰을 사용하는 다른 서비스들 또한 이 토큰을 복호화하고 해당 정보들을 이용할 수 있다.
- 모바일 환경에서도 잘 작동함
(2) 단점
단점은 대부분 장점과의 trade-off들이다.
- 작아야 한다
- 페이로드에 과도하게 정보를 담아 토큰이 너무 커지면 요청의 속도가 느려지고, 토큰 서명 시 암호화 알고리즘 서명 시간 또한 길어진다.
- Payload의 취약한 암호화
- Base64는 디코딩할 수 있다.
- 토큰이 중간에 탈취되는 경우, 내부에 담긴 모든 정보를 확인할 수 있다.
- 따라서 payload에는 사용자에게 sensitive한 정보는 담지 않는 것이 좋다
- 토큰이 탈취 되었을 때의 대처가 어렵다.
- Stateless하므로, 토큰의 임의 삭제가 불가하다.
- 토큰 만료 시간을 넣어서 관리해야 한다.
위와 같이 JWT는 통신 중에 탈취되는 경우 많은 취약점을 가지므로, JWT를 사용할 때에는 반드시 HTTPS를 이용한 트래픽 암호화가 필요하다.