본문 바로가기

개발/JWT

[JWT] Django Rest Framework에서 JWT 사용하기(1)

Django 프로젝트에서 DRF를 이용하여 JWT를 사용하는 방법에 대해 공부해보도록 했다.

 

JWT란?
- Json Web Token의 약자
- Json 포맷을 이용하여 사용자에 대한 정보를 저장하는 클레임(Claim) 기반의 토큰 인증 방식
- 모바일 또는 웹에서 로그인한 사용자들의 정보를 포함하여 인증을 위해 사용되는 암호화 토큰
- 많은 토큰 인증 방식 중 가장 많이 사용되는 토큰 인증 방식

 

사용자 인증 방식 중 토큰 인증 방식인 JWT를 사용하게 된 이유를 알아보도록 하자.

 

옛날에는 일반 토큰 기반을 사용하여 일반 문자열의 형태로 데이터를 보내는 형태로 이루어져 있었는데, 이러한 토큰 인증 방식을 채택하면 다음과 같은 문제점을 갖게 되었다.

 

1. 발급받은 토큰을 만료시킬 방법이 없다.
2. 발급받은 토큰을 검사하거나 처리할 때마다 데이터베이스에 접근해야 하므로 시스템에 부담이 커진다.
3. 모바일 또는 웹 사용자가 로그인 및 로그아웃을 할 경우 토큰을 관리할 방법이 없다.

 

등의 여러 가지 문제점들이 생기게 되어서 새로운 방식을 도입하게 되는데, 그것이 바로 클레임(Claim) 기반의 토큰 인증 방식인 JWT이다.

 

클레임 기반의 토큰 인증 방식 중 하나인 JWT를 사용하게 되면 해당 사용자의 정보나 데이터들을 Json의 형태로 담아서 사용할 수 있게 되어서 위에서 나와있는 일반 토큰 인증 방식의 문제점을 해결할 수 있게 된다.

 

이제 다음 JWT 토큰을 보도록 하자.

 

# JWT 토큰 구조
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjU5NjY2NzkwLCJpYXQiOjE2NTk2NjM3OTAsImp0aSI6Ijk1YWVkY2FkMjg2YTRlOTg5NDU2MjQzMzJhMjE2Y2U3IiwidXNlcl9pZCI6Mn0.s_cUO9XAfzIJlyR_NxVfnIHlsMfqXDs5tFJwBUr8J94

 

해당 JWT 토큰의 구조를 보면 BASE64로 인코딩 되어 긴 형태로 나타나는 것과 일정 구간에서 마침표(.)로 구분되어 나눠지는 것을 볼 수 있다.

 

마침표를 기준으로 나눠보면 다음과 같이 3가지로 나눠지게 된다.

 

# JWT 토큰 구조
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjU5NjY2NzkwLCJpYXQiOjE2NTk2NjM3OTAsImp0aSI6Ijk1YWVkY2FkMjg2YTRlOTg5NDU2MjQzMzJhMjE2Y2U3IiwidXNlcl9pZCI6Mn0.
s_cUO9XAfzIJlyR_NxVfnIHlsMfqXDs5tFJwBUr8J94

 

JWT 토큰의 구조는 위에서부터 차례대로 헤더(Header), 페이로드(Payload), 서명(Verify_Signature)의 총 3가지로 구성되어 있다.

 

JWT 구조에서 각 부분별 담당하는 것들에 대해 알아보자.

 

헤더(Header)
- JWT를 검증하기 위해 필요한 정보를 가진 데이터
- 서명(Signature) 시 사용한 암호화 알고리즘, 토큰 타입,  key_id 등의 정보들을 지닌다.

typ : 해당 토큰의 타입을 지정
alg : 해싱 알고리즘 방식을 지정, 서명(Signature) 및 토큰 검증(HS256, SHA256, RSA)에 사용
# 헤더(Header)
{
  "typ": "JWT",
  "alg": "HS256"
}

 

페이로드(Payload)
- 인증에 필요한 실질적인 데이터들을 저장
- 사용자 정보를 인증할 때 사용하는 데이터 필드(Claim)를 가져와서 인증한다.
- Payload에서 가장 중요한 정보는 토큰 발행 시간(iat)과 토큰 만료 시간(exp)이며, 해당 토큰 만료 시간이 지나면 새로운 토큰을 재발급받아야 한다.
- 인증 시 가져오는 클레임은 등록된 클레임(Registered Claim), 공개 클레임(Public Claim), 비공개 클레임(Private Claim)으로 나뉜다.

등록된 클레임(Registered Claim) : 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터
- iss : 토큰 발급자(issuer)
- sub : 토큰 제목(subject)
- aud : 토큰 대상자(audience)
- exp : 토큰 만료 시간(expiraton), NumericDate 형식으로 구성되어 있다.
- nbf : 토큰 활성 날짜, 해당 날짜가 지나기 전까지 토큰은 활성화되지 않는다.
- iat : 토큰 발행 시간(issued at), 해당 값을 사용하여 토큰 발급 이후 얼마나 시간이 지났는지 확인이 가능하다.
- jti : JWT 고유 식별자, 중복 방지를 위해 사용하고 일회용 토큰(Access Token 등)에 사용된다.

공개 클레임(Public Claim) : 충돌 방지를 위해 URL 포맷을 사용하여 나타낸 데이터

비공개 클레임(Private Claim) : 서버와 클라이언트 사이에 임의로 지정한 데이터
# 페이로드(Payload)
{
  "token_type": "access",  # 토큰의 종류
  "exp": 1659666790,
  "iat": 1659663790,
  "jti": "95aedcad286a4e98945624332a216ce7",
  "user_id": 2  # 실제 사용자의 id값
}

 

서명(Verify_Signature)
- JWT 토큰 자체에 진위여부를 판단하기 위한 용도로 사용된다.
- 토큰을 인코딩하거나 유효성 검증 시 사용하는 고유 암호화 코드이다.
- Header와 Payload 두 가지로는 토큰의 대한 진위여부를 판단할 수 없기 때문에 서명(Signature)을 사용한다.
- Verify_Signature를 생성하기 위해서는 인코딩 된 Header, Payload, Secret_Key 3가지가 필요하다.
# 서명(Verify_Signature)
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret_key
)

 

JWT

 

위 사진처럼 발급받은 토큰을 가지고 헤더(Header), 페이로드(Payload), 서명(Verify_Signature)을 알아보고 싶다면 JWT 사이트에서 확인을 해보면 된다.

 

JWT Link : https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

이제 실제로 DRF에 해당 JWT를 적용시켜보도록 하자.

 

우선 제일 먼저 해야 할 것은 해당 기능을 사용할 수 있도록 JWT 패키지를 설치하는 것이다.

터미널 창에서 다음 명령어를 적어주게 되면 JWT 패키지를 사용할 수 있게 된다.

 

$ pip install djangorestframework-simplejwt

 

이제 설치한 JWT 인증 방식을 사용하기 위해 Django 프로젝트 내에 있는 settings.py에 다음 코드를 추가해준다.

 

# settings.py

INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
    	...
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

 

이제 해당 JWT 인증 방식 적용하고 DRF SimpleJWT에서 기본으로 제공하고 있는 인증 방식을 채택하여 URL 연결을 해주도록 한다.

 

# urls.py

from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    ...
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

 

이후 Postman을 통해 로그인 API를 사용하여 해당 JWT 인증 토큰이 제대로 발급되는지 확인해보았다.

 

JWT 토큰 발급

 

refresh_token과 access_token이 제대로 발급되는 것을 볼 수 있었다.

 

만약 기존에 사용하고 있는 UserView에서 해당 JWT 토큰 인증 방식을 사용하고 싶다면 인증 빙식 클래스를 지정하여 사용해주면 된다.

 

# user/views.py

from rest_framework_simplejwt.authentication import JWTAuthentication

class UserView(APIView):
    authentication_classes = [JWTAuthentication]
    ...

 

이제 Postman에서 해당 유저 정보로 로그인을 한 뒤 Header에 Authorization의 정보로 해당 유저의 access_token 값을 넣어서 해당 유저의 정보를 알아보도록 했다.

 

access_token을 이용한 유저 정보 조회

 

위 사진을 보면 Value의 access_token 앞에 Bearer가 붙는 것을 볼 수 있는데, access_token을 이용하여 유저 정보를 조회하고자 할 때에는 Bearer를 붙여서 해당 토큰 인증 방식이 access_token임을 명시해주어야 한다.

 

만약 위처럼 access_token을 가지고 해당 유저의 정보를 조회하고자 할 때 access_token의 유효 기간이 만료되어 사용하지 못하게 되는 경우가 생길 수 있다.

이때, refresh_token을 이용하여 다시 access_token을 재발급받아서 사용할 수 있다.

 

refresh_token을 이용한 access_token 재발급

 

위에서 재발급받은 access_token을 가지고 해당 유저의 정보를 다시 조회해보도록 하자.

 

재발급된 access_token을 가지고 유저 정보 조회

 

DRF에서 제공하는 기본적인 JWT 세팅에서 access_token 유효 시간이 5분, refresh_token 유효 시간이 1일로 지정되어 있는데, 만약 해당 JWT 토큰 인증 방식의 세부적인 옵션을 설정해주고 싶다면 settings.py에서 다음과 같이 코드를 작성하여 세부적인 설정을 해줄 수 있다.

 

# settings.py

from datetime import timedelta

SIMPLE_JWT = {
    # Access 토큰 유효 시간 설정하기
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50),
    # Refresh 토큰 유효 시간 설정하기
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),

    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': False,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,
    'JWK_URL': None,
    'LEEWAY': 0,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

 

timedelta를 이용하여 access_token과 refresh_token의 유효 시간을 설정해줄 수 있고, 이외에도 다른 세부적인 설정들에 대해 지정을 해줄 수도 있었다.

'개발 > JWT' 카테고리의 다른 글

[JWT] Django Rest Framework에서 JWT 사용하기(2)  (0) 2022.08.05