JSON WebToken
유저를 인증하고 식별하고 JSON 형식의 데이터를 안전하게 전송하기 위한 토큰 기반의 인증 시스템이다.
특징
- 세션과 다르게 서버가 아닌 클라이언트에 저장되어 메모리, 스토리지 등 세션을 관리했던 서버의 부담이 줄어든다.
- 토큰 자체에 데이터가 담겨있어 판별가능하다.
- 상태를 유지하지 않아 Stateless하다.
인증 방식

- 유저가 로그인 요청을 한다.
- 서버 단에서 유저(클라이언트)에게 유일한 토큰을 발급한다.
- 클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해두고, 서버에 요청할 때마다 해당 토큰을 HTTP 헤더에 포함시켜 전달한다.
- 서버는 전달받은 토큰을 검증하고 요청에 응답한다
- 토큰에 요청한 사람의 정보가 담겨있으므로 DB조회 없이 유저 식별이 가능하다.
JWT 구조
. 을 구분자로 나누어지는 세가지 문자열로 구성되어있다.
- Header . Payload . Signature
Header
JWT 에서 사용할 타입과 해시 알고리즘의 종류를 담고있다.
{
'alg' : 'HS256',
'typ' : 'JWT'
}
- alg : 서명 암호화 알고리즘
- typ : 토큰 유형
Payload
서버에서 첨부한 사용자 권한 정보와 데이터를 담고있다.(Claim)
Claim : 토큰에서 사용할 정보의 조각(k:v 형식)
{
'jti' : '1000',
'exp' : '1532500000000',
'<http://wwww.com>': true,
username': 'z0'
}
정해진 데이터 타입은 없고 대표적으로 Registered claims, Public claims, Private claims 이 있다.
- Registed claims : 미리 정의된 클레임.
- iss(issuer; 발행자),
- exp(expireation time; 만료 시간),
- sub(subject; 제목),
- iat(issued At; 발행 시간),
- jti(JWI ID)
- Public claims : 사용자가 정의할 수 있는 클레임 공개용 정보 전달을 위해 사용한다.
- Private claims : 해당하는 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임. 외부에 공개되도 상관없지만 해당 유저를 특정할 수 있는 정보를 담고있다.
Signature
Header, Payload 를 Base64 URL-safe Encode 를 한 이후 Header 에 명시된 해시함수를 적용하고, 개인키(Private Key)로 서명한 전자서명을 의미한다.
- Header와 Payload는 단순히 인코딩 된 값으로 제 3자가 복호화 및 조작이 가능한 반면 Signature은 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 불가능하다. 즉 Signature는 토큰의 위변조 여부를 확인하는데 사용된다.
Access Token & Refresh Token
토큰 탈취 위험성을 줄이기 위해 기본 JWT 방식에서 인증 보안을 강화시킨 방식의 토큰이다. 두가지 토큰 모두 존재해야 하는 것은 아니고 하나가 없을 수 있으며 성격이 전혀 다른 토큰이다.
Access Token
클라이언트가 갖고있는 실제로 유저의 정보가 담긴 토큰이다. 클라이언트에서 요청이 오면 서버에서 해당 토큰에 있는 정보를 활용하여 사용자 정보에 맞게 응답을 진행한다.
Refresh Token
새로운 Access Token을 발급해주기 위해 사용하는 토큰이다. 짧은 수명을 가지는 Access Token에게 새로운 토큰을 발급해주기 위해 사용하며 해당 토큰은 보통 데이터베이스에 유저 정보와 같이 기록된다.
인증 방식
- 로그인시, 서버는 로그인을 성공시키면서 클라이언트에게 Access Token과 Refresh Token 을 동시에 발급해준다.
- 서버는 데이터베이스에 Refresh Token을 저장하고, 클라이언트는 Access Token과 Refresh Token을 쿠키, 세션 혹은 웹스토리지에 저장하고 요청이 있을때마다 이 둘을 헤더에 담아서 보낸다.
- 만료된 Access Token을 서버에 보내면, 서버는 같이 보내진 Refresh Token을 DB에 있는 것과 비교해서 일치하면 다시 Access Token을 재발급한다.
- 로그아웃을 하면 저장소에서 Refresh Token을 삭제 하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서 다시 재발급해서 DB에 저장한다.
Simple JWT
가장 활발하게 업데이트되는 django restframework jwt 라이브러리이다. 내장 시리얼라이저와 뷰를 커스텀 해서 사용할 수 있다.
TokenObtainPairSerializer
class TokenObtainPairSerializer(TokenObtainSerializer):
@classmethod
def get_token(cls, user):
return RefreshToken.for_user(user)
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
if api_settings.UPDATE_LAST_LOGIN:
update_last_login(None, self.user)
return data
👉🏻 get_token 함수에서 user에 맞는 refresh token을 생성
👉🏻 get_token 함수에서 get_token[key] value로 payload 클레임 수정이 가능하다.
👉🏻 validate 함수에서 refresh token을 확인 후 user의 refresh token, access token 리턴해준다 . validate 함수는 로그인 성공
시 리스폰스로 보여줄 데이터이므로 유저아이디나 정보를 추가해줄 수 있다.
TokenObtainPairView
Simple jwt 커스텀 로그인을 위해 필요한 뷰이다.
class SigninView(TokenObtainPairView):
serializer_class = TokenOptainPairSerializer
TokenRefreshView
simple jwt access 토큰을 재발급을 하기 위해 사용하는 뷰이다. url에 뷰를 임포트 해주고 path 추가해주면 바로 사용할 수 있고, refresh token을 넘겨주면 settings에서 설정한 ROTATE_REFRESH_TOKEN 변수 값에 따라 새로운 access 이나 access, refresh 모두 재발급 된다.
수동으로 토큰 재생성
jwt 처리를 미들웨어에서 처리해주기 위해 따로 뷰를 임포트하지 않고 수동으로도 토큰 재발급 해줄 수 있어서 이 방법으로 사용했다.
from rest_framework_simplejwt.tokens import RefreshToken
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
👉🏻 user에 대한 새로운 access, refresh 토큰의 직렬화된 표현을 반환
Simple JWT 토큰 유효성 검사
access_token = request.COOKIES['access_token']
payload = token_backend.decode(access_token, verify=True)
- simple jwt에 토큰 유효성 검사를 해주는 내장 view가 존재하는데 나는 사용하지 않았다. 토큰이 만료되면 자동으로 삭제되기 때문에 decode가 되는 토큰이라면 유효하다고 판단하기 때문에 decode 단계에서 verify 옵션을 주어 추가 유효성 검사 단계만 수행 ( exp 클레임 확인 )
'Dev. > Django.' 카테고리의 다른 글
| [DRF] Serialization / Deserialization 동작 변경 - to_internal_value와 to_representation (0) | 2023.04.17 |
|---|---|
| [Django] ManyToMany 모델 인스턴스 생성 및 삭제하기- add, clear (0) | 2023.04.14 |
| [Django] Django에서 fixture 사용 (0) | 2023.03.31 |
| [Django] Django Middleware (0) | 2023.03.16 |
| [Django] DRF로 pagination 적용하기 -PageNumberPagination (0) | 2023.03.16 |