본문 바로가기

백앤드 프로그래밍/JWT

[JWT] 토큰 기반 인증과 JSON Web Token

토큰 기반 인증이란?

기존의 세션 기반 인증과는 다르게 서버가 사인한 토큰을 이용하여 인증을 수행하는 방식이다.

기존의 세션 기반 인증은 세션을 유지하면서 세션을 DB에 저장한다면 DB 쿼리가 발생하고, 메모리에 저장한다면 메모리가 부족해 질 수 있는 등 서버에 부하가 발생할 가능성이 높지만 토큰 기반 인증은 서버가 전달받은 토큰을 검증만 하면 되기 때문에 stateless 하게 서버의 상태를 유지시켜 서버의 부담을 줄여주고 서비스의 확장성을 높일 수 있다.

토큰 방식의 문제

세션에 비해서 토큰이 장점만 가지고 있는 것 같지만 단점도 존재한다. stateless한 토큰의 특성 때문에 토큰을 강제로 만료시킬 수 없는 문제를 가지고 있다.

이 점 때문에 나는 다음과 같은 두가지 문제에 대해 고민해 보았다.

1. 액세스 토큰과 리프레시 토큰에 관한 고민

서버가 앨리스에게 발급한 토큰이 밥에게 탈취되었다고 가정해보자. 밥은 토큰이 만료될 때 까지 앨리스인 척 행동할 수 있을것이다.

위와 같은 상황을 해결하기 위해 토큰의 만료 주기를 너무 짧게 하면 수시로 로그인을 다시 하게 되어 사용자가 불편해지고, 그렇다고 사용자 편의를 위해 만료 주기를 길게 하면 토큰이 탈취당했을 때 피해가 커지게 된다.

이러한 토큰 인증 방식의 단점을 보완하기 위해 보통은 토큰의 타입을 리프레시 토큰액세스 토큰으로 나누어 사용하는 방식을 택한다.

이 방식은 서비스를 요청하는데 사용하는 만료 주기가 짧은 액세스 토큰액세스토큰을 재발급받을 수 있는 보안이 철저하고 만료 주기가 긴 리프레시 토큰을 사용한다.

액세스 토큰은 탈취당하더라도 보안이 철저한 리프레시 토큰을 탈취하지 못하면 밥이 앨리스로 행세할수 있는 시간은 얼마 되지 않기 때문에 피해가 적어진다.

2. 로그아웃에 대한 고민

앨리스가 로그아웃하려고 할 때 세션 기반 방식이라면 세션을 파기할 것이다.

그러나 토큰 방식은 토큰을 강제로 만료시킬 수 없기 때문에 블랙리스트를 사용해 로그아웃 요청으로 전달된 토큰을 등록하여 해당 토큰을 통한 요청을 거부한다.

그러나 이렇게 한다면 서버가 블랙리스트에 토큰을 등록하고 매 요청마다 블랙리스트를 조회해야 하기 때문에 서버에 부하가 걸려 토큰 기반 인증을 사용하는 의미가 희석된다.

그러나 앞에서 언급한 액세스-리프레시 토큰 방식을 사용한다면 빨리 만료되는 액세스토큰은 클라이언트에서 삭제하고,  리프레시 토큰만 블랙리스트에 등록해 리프레시 요청 시에만 블랙리스트를 체크하게 하면 서버의 부하를 줄이면서도 블랙리스트 방법을 사용할 수 있을 것 같다.

JWT란?

JWT(Json Web Token)은 JSON 객체를 이용한 토큰 기반 인증의 웹 표준(RFC 7519) 구현체이다. JWT는 다음의 두가지 특성을 가지고 있다.

자가 수용적

필요한 모든 정보를 모두 가지고 있다. 사용한 암호화 알고리즘부터 정보와 디지털 서명까지 JWT 안에 모두 포함된다.

컴팩트 사이즈

작은 사이즈를 가지고 있어 HTTP 헤더와 url 인코딩에도 실을 수 있으며 전송 속도가 빠르다.

JWT의 사용

1. 사용자 인증

서버는 클라이언트가 보낸 JWT를 전달받았을 때 시그니처가 올바르면 페이로드의 정보를 이용해 작업을 처리한다.

2. 메시지 전송

시그니처를 이용해 메시지의 위변조를 막을수 있어 메시지의 안전한 전송이 보장된다.

JWT의 구조

JWT는 다음과 같이 헤더, 페이로드, 시그니처의 세 부분을 각각 base64 인코딩한 것을 끝점으로 이어붙인 문자열로 이루어져 있다.

header.payload.signature

1. 헤더

헤더는 토큰 타입과 시그니처를 만들때 사용된 암호화 알고리즘의 정보가 실려있다. 다음은 헤더의 예시이다.

{
    "alg": "HS256",
    "typ": "JWT"
}

2. 페이로드

페이로드는 권한이나 사용자 정보 등 토큰이 가지는 정보를 기록해놓은 부분이다. 페이로드에서 key-value로 구성된각각의 요소를 클레임이라고 하는데 클레임에는 세가지 타입이 있다.

registered

등록된 클레임들은 토큰의 정보를 담기 위해 사용되는 미리 약속된 클레임이다. 등록된 클레임의 이름과 정보들은 여기서 확인해 볼 수 있다.

public

충돌 방지를 위해 uri 형식의 key를 가지고 있는 클레임이다.

private

서버와 클라이언트가 미리 협의하여 사용하는 클레임이다.

다음은 기본적인 페이로드의 예시이다.

{
    "iss": "artoria.us",
    "exp": "1383260000000",
    "https://artoria.us/user": true,
    "username": "pendragon"
    "is_admin": true
}

여기서 Flask-JWT-extended 라이브러리 같은 경우에는 access-refresh 구조 사용시 기본적인 클레임으로 type과 refresh라는 두가지 프라이빗 클레임을 넣어서 사용한다. 

3. 시그니처

시그니처는 토큰이 올바른지를 검증하기 위해 사용된다. 헤더와 페이로드를 인코딩한것을 합쳐 서버가 가진 비밀키로 해시하여 만든다.

서버는 이 시그니처를 검증하여 페이로드에 실린 정보가 위변조 되지 않았는지 체크한다.

마치면서.

참고한 자료는 다음과 같다.

https://velopert.com/2389

http://bcho.tistory.com/999

https://swalloow.github.io/implement-jwt