토큰 기반 인증은 기존의 세션 기반 인증이 가지고 있던 한계를 극복하고자 고안되었다.
세션 기반 인증의 한계
세션 기반 인증은 서버에서 유저의 상태(state)를 관리한다. 그래서 서버의 부담이 되었고, 사용자의 인증 상태를 클라이언트에 이를 저장하는 방법을 고민하게 되었고, 그 결과 토큰 기반 인증 방식이 등장하게되었다.
토큰은 유저의 인증 상태를 클라이언트에 저장할 수 있어서, 세션 인증 방식의 비교해 서버의 부하나 메모리 부족 문제를 줄일 수 있다.
- POST /login - 아이디와 패스워드를 사용해 로그인 요청
- 인증 정보 검증
- 유효하다면(인증을 했다면) 해당 유저에 대한 세션 데이터를 서버 메모리에 저장
- 쿠키에 세션 ID를 넣어 클라이언트에 전달
- 클라이언트에 세션 ID 저장
- GET /someinfo - 쿠키에 세션 ID를 담아 요청
- 해당 세션 ID를 가진 세션 데이터를 조회
- 세션이 유효하다면 요청에 대한 응답 데이터 전송
Token
은 교통 승차권 같이 무언가를 이용할 수 있는 권한이나 자격을 나타내는 일종의 증표다.
토큰 인증 방식은
최근 웹 애플리케이션에서 많이 사용되는 인증 방식 중 하나다. 토큰을 사용하면 사용자의 정보를 클라이언트 측에 저장할 수 있다.
- POST /login - 아이디와 패스워드를 사용해 로그인 요청
- 인증 정보 검증
- 유효했다면(인증을 했다면) 해당 유저에 대한 토큰을 생성 (+ 서버의 비밀 키)
- 클라이언트에 토큰 전달 (Authorization 헤더)
- 클라이언트에 토큰 저장 (브라우저 세션 스토리지나 로컬 스토리지 등에 저장)
- GET /someinfo - 요청과 함께 토큰 전달 (Authorization 헤더)
- 유효한 토큰인지 검증 (서버에 비밀키로 검증)
- 토큰이 유효하다면 클라이언트 요청에 대한 응답 데이터 전송
JWT (JSON Web Token)
는 데이터 전송에 사용하는 토큰 기반 인증 기술로 JSON 형식으로 저장한 정보(payload)를 암호화(서명)하여 전송한다.
클라이언트가 서버에 요청을 보내고, 인증정보를 암호화된 JWT 토큰으로 제공하고, 서버는 이 토큰을 검증하여 인증정보를 확인할 수 있다.
JWT는 .
으로 나누어진 세 부분이 존재하며 각각을 Header, Payload, Signature라고 부른다.
Header
에는 HTTP의 헤더처럼 해당 토큰 자체를 설명하는 데이터가 담겨 있다.- 토큰의 종류, 그리고 시그니처를 만들 때 사용할 알고리즘을 JSON 형태로 작성한다.
{ "alg": "HS256", "typ": "JWT" }
이 JSON 객체를
base64
방식으로 인코딩하면 JWT의 첫 번째 부분인 Header가 완성된다.
- base64 방식은 원한다면 얼마든지 디코딩할 수 있는 인코딩 방식이다.
- 따라서 비밀번호와 같이 노출되어서는 안 되는 민감한 정보를담지 않도록 해야 한다.
Payload
는 HTTP의 페이로드처럼 전달하려는 내용물을 담고 있는 부분이다.- 어떤 정보에 접근 가능한지에 대한 권한, 유저의 이름과 같은 개인정보, 토큰의 발급 시간 및 만료 시간 등의 정보들을 JSON 형태로 담는다.
{ "sub": "someInformation", "name": "phillip", "iat": 151623391 }
이 JSON 객체를
base64
로 인코딩하면 JWT의 두 번째 부분인 Payload가 완성된다.
Signature
는 토큰의 무결성을 확인할 수 있는 부분이다.- Header와 Payload가 완성되었다면, Signature는 이를 서버의 비밀 키(암호화에 추가할 salt)와 Header에서 지정한 알고리즘을 사용하여 해싱한다.
예를 들어, 만약 HMAC SHA256 알고리즘을 사용한다면 Signature는 아래와 같은 방식으로 생성된다.
HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
따라서 누군가 권한을 속이기 위해 토큰의 Payload를 변조하는 등의 시도를 하더라도 토큰을 발급할 때 사용한 Secret을 정확하게 알고 있지 못한다면 유효한 Signature를 만들어낼 수 없기 때문에 서버는 Signiture를 검증하는 단계에서 올바르지 않은 토큰임을 알아낼 수 있다.
Signature을 사용해서 위조된 토큰을 알아낼 수는 있지만, 토큰 자체가 탈취된다면 토큰 인증 방식의 한계가 드러난다.
인증 상태를 관리하는 주체가 서버가 아니므로, 토큰이 탈취되어도 해당 토큰을 강제로 만료시킬 수 없다. 따라서 토큰이 만료될 때까지 사용자로 가장해 계속해서 요청을 보낼 수 있다.
토큰이 탈취되는 상황을 대비해서 유효 기간을 짧게 설정하면, 사용자는 토큰이 만료될 때마다 다시 로그인을 진행해야 하기 때문에 좋지 않은 사용자 경험을 제공한다.
그렇다고 유효 기간을 길게 설정하면 토큰이 탈취될 경우 더 치명적으로 작용할 수 있다.
토큰에 여러 정보를 담을 수 있는 만큼, 많은 데이터를 담으면 그만큼 암호화하는 과정도 길어지고 토큰의 크기도 커지기 때문에 네트워크 비용 문제가 생길 수 있다.
토큰 인증의 한계를 극복하기 위해 다양한 방법들이 고안되었지만 이 중 대표적인 구현 방법은 액세스 토큰과 리프레시 토큰을 함께 사용하는 것이다.
간단하게 액세스 토큰은 실제 출입용, 리프레시 토큰은 액세스 토큰 재발급용이라고 생각할 수 있다.
두 가지의 각기 다른 토큰을 사용하는 경우, 액세스 토큰이 만료되더라도 리프레시 토큰의 유효기간이 남아있다면 사용자는 다시 로그인을 할 필요 없이 지속해서 인증 상태를 유지할 수 있다.
하지만, 리프레시 토큰도 모든 문제를 해결해주지 않는다.
리프레시 토큰은 긴 유효 기간을 가지고 있어 해당 토큰마저 탈취된다면 토큰의 긴 유효 기간 동안 악의적인 유저가 계속해서 액세스 토큰을 생성하고 사용자의 정보를 해킹할 수도 있기 때문이다.
이를 대비하기 위해서는 리프레시 토큰을 세션처럼 서버에 저장하고 이에 대한 상태를 관리하기도 한다.
결국 이 세상에 완벽한 보안 방법은 없다.
세션, 토큰 등 다양한 보안 방식 및 여러 구현 방법들은 절대 뚫리지 않는 궁극의 보안을 위해 만들어진 것은 아니며, 여러 방식들은 단순히 보안뿐만 아니라 보안과 사용자 경험 사이의 적절한 균형을 찾기 위해 만들어졌다.
Reference