JWT 와 TokenAuthentication
Django REST Framework 에서 제공하는 Authentication 즉, 인증 방식 중
Token 을 통해 User를 인증하는 방식인 TokenAuthentication 이 있다.
하지만, Token을 통한 인증 방식 보다는 JWT(Json Web Token)를 통한 인증방식을 더 많이 사용한다.
왜 그럴까?
JWT 와 Token 인증 차이
Token 인증 방식보다 JWT 인증 방식을 더 많이 사용하는 이유를 알아보기 전에
먼저 서로의 차이점을 알아보자.
JWT 는 Json Web Token 의 약자로 JWT 역시 Token 의 종류 중 하나이다.
그럼 이러한 질문이 생길 수 있다.
Q. 그럼, Token 의 한 종류면 JWT와 Token은 똑같은 것 아닌가..?
A. NO! 이는 Token을 관리하는 방식이 다르다.
JWT 와 Token 모두 Stateless 즉, 사용자 상태를 저장하지 않다는 것은 같지만
- TokenAuthentication
- 서버에서 Token 을 생성하고 토큰 값을 데이터베이스에 저장.
- 정해진 규격이 없이 User를 식별 가능한 고유한 값으로 Token 생성
- JWT
- 서버에서 Token 을 생성하고 토큰에 서명.
- 페이로드(payload)에 User 식별 정보, 만료일 등을 포함한 표준 규격으로 생성
즉, Token 인증 방식은 서버가 데이터베이스에 Token의 정보를 저장하고 검증하는 반면
JWT 인증 방식은 서버에서 서명한 것을 검증하고, payload 에 저장된 정보를 확인하여
User 식별한다는 차이가 있다.
Stateless? Stateful?
Stateless 즉, 무상태란 서버가 클라이언트의 상태를 저장하지 않고 단순히 독립적인 요청과 요청에 대한 응답만 존재하는 것을 의미한다.
Stateful 즉, 상태유지란 서버가 클라이언트의 상태를 보존하고 이전 요청이 정상적으로 이루어졌을 때, 다음 요청과 이전 요청이 연관되는 것을 의미한다.
JWT 와 MicroService
마이크로서비스 같은 분산된 환경에서는 하나의 데이터베이스만 사용하는 것이 아니라 각 서비스 마다 독립적인 데이터베이스를 가지는 경우가 많아 데이터베이스에 저장된 Token의 정보를 공유하기 힘들어 마이크로서비스 같은 환경에서는 JWT 인증 방식을 선호한다.
예시를 통해 생각해보자.
만약 A 서버에서 kimjihong 이라는 User가 로그인 해서 A 서버에서 Token 을 발행했다면
kimjihong 이라는 사용자의 Token 정보는 A 서버의 데이터베이스에 저장되었다.
만약 kimjihong 이라는 사용자가 B 서버 혹은 다른 서버로 접속하게 된다면
해당 서버에서는 kimjihong 의 Token 유효성 판단을 위해 다른 서버에 Token 확인 요청을 해야한다.
이 문제를 해결하기 위한 방안은 모든 서버에 Token 을 발급받는 방법이 있긴 하지만
결국 데이터베이스 측면에서 보면 비효율 적이게 된다.
이러한 경우 때문에 데이터베이스가 분리된 환경에서는 SessionAuthentication 이나 TokenAuthentication 보다는 JWTAuthentication 을 선호하게 된 것이다.
하지만, 그렇다고 JWTAuthentication 가 무조건 좋은 것은 아니다.
JWT 의 경우 토큰 자체에 정보가 포함되기 때문에 Token을 탈취 당하면 문제가 생기게 된다.
따라서, JWT 인증 방식을 사용시 어떻게 안전하게 보관하고 사용할지 생각해야한다!
이 문제에 대해서는 아래에서 다루겠다.
JWT 구조
그럼 윗 문단에서 말한 Payload 란 대체 무엇일까?
먼저 표준 규격으로 생성되는 JWT의 구조를 그림으로 보자!
JWT 의 구조는 Header(헤더), Payload(페이로드), Signature(서명) 3가지 부분으로
각 부분은 BASE64로 인코딩되어 . 을 통해 각 항목을 구분하고 있다.
JWT Header
JWT 의 Header 는 Signature 부분을 Hashing 할 알고리즘 alg
토큰의 형태(Tpye) 을 지정하는 typ 으로 이루어져 있다.
Hashing 알고리즘은 보통 HS256 을 사용하지만, 위 사진과 같이 여러 알고리즘을 지원한다.
JWT Payload
JWT의 Payload 는 JWT 에 포함해서 전달할 데이터들을 말한다.
즉, JWT는 서버에서 직접 관리하는게 아니라 JWT 자체에 JSON 형태의 데이터를 포함하는데
이 데이터들을 담는 부분이 Payload 이고, 각 데이터를 클레임(Claim) 이라고 부른다.
Claim 의 종류
- Registered Claim
예약어로 미리 정의된 레지스터 클레임 - Custom claims
사용자 정의 클레임- Public claims
표준화되지 않은 사용자 정의 정보를 나타내는 클레임 - Private claims
서버와 클라이언트 간에 협의되어 사용되는 클레임
- Public claims
Registered Claim
레지스터 클레임은 Token 의 만료일, JWT 고유 식별자 등 토큰에 대한 정보를 표현하기 위해
예약어로 미리 정해진 클레임을 의미한다.
- iss - JWT 발행자
- sub - 토큰의 주제
- aud - 토큰 대상
- exp - 토큰 만료일
- nbf - 토큰 활성일
- iat - 토큰 발급일
- jti - 토큰 고유 식별자
Custom - Public claims
공개 클레임은 애플리케이션 간에 공유되는 일반적인 정보 나타내는 클레임이다.
예를 들어, User 의 역할이나 유형, 권한 정보를 말한다.
공개 클레임은 클레임 이름이나 값의 충돌을 방지하기 위해서 보통 URI 형식으로 표현한다.
ex) "http://127.0.0.1/is_admin": True
Custom - Private claims
비공개 클레임은 서버와 클라이언트 간에 협의하여 사용하는 클레임으로 보통 서버와 클라이언트가
서로 정보 교환을 할 때 사용한다.
ex) "user_id": 1
JWT Signature
JWT 의 Signature 은 서버가 해당 JWT 토큰의 무결성을 판별하기 위해 서버가 JWT 발급시
HMAC 이나 RSA 암호화 알고리즘을 통해 헤더와 페이로드에 대한 서명을 생성하고
BASE64로 인코딩하여 JWT 를 생성한다.
- HMAC 을 통한 서명
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) - RSA 을 통한 서명
RSASign(base64UrlEncode(header) + "." + base64UrlEncode(payload), private_key)
서버에서 JWT 검증시 Header 에서 정의한 Hashing 알고리즘을 통해 서명을 검증하여
JWT 의 무결성을 확인한다.
JWT의 한계점
JWT 는 Session 과 달리 Stateless 하기 때문에 따로 사용자 정보를 저장하지 않는다.
따라서, JWT 기반의 Authentication 시스템을 구축하게 되었을 때
서버는 User가 로그인시 JWT 를 발행해주고 이후 모든 관리는 클라이언트에게 맡긴다.
여기서 문제가 발생한다.
클라이언트에게 이후 모든 관리를 맡길 경우
악의적인 누군가가 JWT 를 탈취하기 위해 XSS, CSRF 등의 공격을 통해 JWT 를 탈취 했다면
악의적인 누군가는 탈취한 JWT 을 가지고 JWT 가 만료될 때 까지 해당 사용자 행세를 할 수 있는 것이다.
Session 방식의 경우 Stateful 즉, 서버가 클라이언트의 상태를 저장하기 때문에
서버가 탈취 당했다고 생각하면, session 저장소에서 session 을 지우는 대처가 가능하지만
JWT 방식은 이러한 대처가 어렵다.
XSS, CSRF 공격은 뭘까?
XSS(Cross-Site Scripting) 은 공격자가 악의적인 Script 구문을 웹 페이지에 삽입하여 User 브라우저에서 실행시키는 공격이다. XSS는 Cookie 나 Session 토큰 등 민감한 정보를 탈취하려는 목적으로 Stored XSS와 Reflected XSS 그리고 DOM-based XSS 공격 방법이 있다.
CSRF(Cross-Site Request Fogery) 은 악의적인 누군가가 User의 권한을 사용하여 의도하지 않은 요청을 서버에 보내는 공격을 말한다.
참고 : https://lucete1230-cyberpolice.tistory.com/23
Session 은 어떻게 탈취를 인식할까?
세션 방식은 클라이언트의 상태를 저장하기 때문에 다른 국가에서의 로그인 또는 User Agent 정보의 큰 변화 등 request 클라이언트의 상태 변화를 모니터링하여 탈취를 인식할 수 있다.
이는 Session 이 Stateful 하기 때문에 가능한 것
그럼 JWT 방식의 경우엔 눈 뜨고 보고만 있어야 할까? ㅠ_ㅠ
Solution 1) exp 기간을 줄이기
레지스터 클레임에서 잠깐 본 Expiration Time 즉, 만료기간을 크게 줄여 Token 을
악의적인 사용자에게 탈취 당했어도 만료 시간이 얼마 남지 않아 최소한의 보안을 유지할 수 있다.
( Simple JWT 의 경우 만료기간을 5분으로 권장하고 있다. )
하지만, 이렇게 짧은 만료기간을 가져감으로 써 새로운 문제가 생긴다.
만약 Velog와 같은 블로그에서 글을 쓰고 있는 경우, 글을 쓰는 도중에
Token이 만료되면 작성중인 게시글은 날아가고 다시 로그인 화면으로 이동될 것이다.
이렇게 된다면 그 누구도 해당 블로그는 다신 이용하지 않을 것이다.
Solution 2) Sliding Session
만약 어떤 사용자가 시간이 오래걸리는 특정한 Action 을 취한다면
토큰의 만료일을 일정 시간 늘려주는 방법으로 위 문제를 개선할 수 있다.
예를 들어, User가 게시글 작성 버튼을 누르면 기존의 5분 이였던 만료기간을 1시간으로
늘린 새로운 Token 을 발급해줘서 작성중인 게시글을 날리는 일을 개선할 수 있다.
하지만, 만약 User가 게시글을 작성한다고 서버에 요청을 보내 만료기간이 긴 Token을 발급받고
게시글을 작성하지 않는다면 긴 만료시간으로 인해 또 다시 Token 탈취 문제가 발생한다.
Solution 3) Refresh Token
그래서 JWT 을 사용할 때 대부분 Access Token 과 새로운 Access Token 을 발급할 수 있는 Refresh Token 도 같이 사용한다.
- Access Token
- User가 리소스에 접근하기 위해 사용되는 Token
- Refresh Token
- 만료 기간이 짧은 Access Token 을 갱신하기 위한 만료 기간이 긴 Token
기간이 짧은 Access Token 만 사용하는 대신에 Token 을 다시 갱신할 수 있고 만료일이 긴
Refresh Token 을 클라이언트로 같이 보내줌으로 써 Access Token 탈취 문제를 보안할 수 있다.
하지만, 여기서 치명적인 문제가 발생한다..
Refresh Token 자체를 탈취 당하면 악의적인 사용자는 긴 유효 기간동안 계속해서
Access Token 을 발급 받을 수 있어 Refresh Token 탈취시 매우 치명적이다.
Solution 4) Refresh Token Rotation & Blacklist
위 모든 문제를 보안하기 위해 고안된 방법이 Refresh Token Rotation 과 Blacklist 이다.
- Refresh Token Rotation
- 서버가 Refresh Token 을 통해 새로운 Access Token 을 발급시 새로운 Refresh Token 과 Access Token 을 함께 주는 기능이다.
- Blacklist
- 특정 Token 을 차단하기 위해 저장한 Token list 이다.
즉, Refresh Token Rotation 과 Blacklist 를 사용하면
서버는 Refresh Token 토큰을 데이터베이스 내의 Blacklist 에 있는지 확인하며 유효성을
검증하고, 해당 Token 이 유효하다면 새로운 Access Token 과 Refresh Token 을 발급한 뒤
사용한 Refresh Token 은 Blacklist 에 등록해 재사용을 막은것이다.
Solution 5) HttpOnly & SameSite & Secure
위 4가지 해결 방안을 사용해 최소한의 보안성을 갖출 수 있지만 근본적으로 100% 안전하게
XSS 나 CSRF 등의 공격에서 벗어나기엔 한계가 있다.
그래서 보통 HttpOnly, SameSite, Secure 같은 옵션을 추가로 사용하여 운영하는 편이 좋다.
- HttpOnly (HTTP 전용 쿠키)
XSS 방어 목적으로 Script 에 의해 Cookie 에 접근하는 것을 막는 옵션 - SameSite (동일 출처 정책)
CSRF 방어 목적 쿠키를 어떤 상황에서 전송할지 제어하는 옵션 - Secure (보안 쿠키)
Cookie의 안정성 목적 HTTPS 프로토콜일 때만 Cookie 를 전송하는 옵션
HttpOnly 옵션 사용시 Authorization 헤더에 Access Token 포함 하는 방법
So how can we securely utilize JWT?
위 해결책이 100% 안전하게 JWT를 운영하는 방법은 아니지만 JWT에서 지원하는 보안 옵션을
확인해 보고 내가 어떻게 서비스를 구현할지 생각해서 사용해 좀 더 안전하게 운영해야 한다.어쩌피 완벽한 보안은 어디에도 없다.
또, 내가 JWT를 라이브러리를 통해 구현중이라면 해당 라이브러리가 계속해서 업데이트를 하고 있는지 확인해서 사용하는 편이 좋다!
Django REST Framework 의 경우 Simple JWT 라이브러리를 사용해 JWT 를 구현할 것을 권장하고 있다.
Ref.
https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims#private-claims
https://www.devkuma.com/docs/jwt/
http://www.opennaru.com/opennaru-blog/jwt-json-web-token-with-microservice/
https://brunch.co.kr/@jinyoungchoi95/1
https://velog.io/@jiseung/Authentication-Strategies
https://lucete1230-cyberpolice.tistory.com/23
출처: https://velog.io/@kimjihong/JWTAuthentication#jwt-%EC%99%80-tokenauthentication
'Network > Web' 카테고리의 다른 글
API Gateway를 이용한 인증과정 (1) | 2024.03.17 |
---|---|
JWT 토큰이란? (1) | 2020.05.08 |