API는 클라이언트가 올바른 클라이언트인지 확인하기 위해 인증 절차를 거치고 API를 이용하도록 한다.
django 프로젝트를 시작하고 나면 기본적으로 Basic 인증 방식을 거치게 된다. 하지만 이 방식은 보안에 매우 취약해 개발 목적으로만 사용한다.
다른 인증 방식으로는 Session, Token 인증 방식이 있다.
두 인증 방식의 공통점은 처음 아이디와 패스워드를 통해 인정 요청을 보냈을 때 서버에게 받은 Token을 세션 또는 쿠키(브라우저)에 저장한다는 점이다.
이 토큰을 헤더에 포함하고 다른 API 요청을 보내면 서버는 Token이 등록됨을 확인한 후 데이터를 제공한다.
만약 토큰을 포함하지 않는다면 다음과 같은 에러를 보게 된다.
이 두 방식의 단점은 쿠키나 세션 IP가 탈취 당하면 손을 못쓴다.
JWT 인증 방식은 이 둘과 다르게 서버에 따로 Token을 보관하지 않는다. 서버로 부터 생성된 JWT Token은 유저만 보관하고 JWT 해독 방식에 의해 인증이 된다.
JWT는 header, payload, signiture로 구성되어 있다.
JWT 유효성 검사는 header + payload + 서버 key 로 만들어진 signiture의 비교로 이루어진다. 3가지가 일치하면 서버는 정상적으로 API를 제공한다.
게다가 시간이 지나면 access jwt가 파기되며 refresh jwt를 이용해서 재생성해야한다.
이 JWT를 통해 인증을 하는 방식이 JWT Authentication이다.
from datetime import timedelta
REST_FRAMEWORK = {
# 기본적인 인증 방식은 JWTAuthentication으로 한다.
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
# 기본적인 권한은 인증이 되면 제공하는 식으로 한다.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
# JWT 설정
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'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(days=50),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=100),
}
루트 url
urlpatterns = [
...
# 차례대로 token 생성, 재생성, 확인
path('api/token/', TokenObtainPairView.as_view(), name='token_refresh'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify')
]
이 프로젝트를 하면서 가장 의아했던 점이 있다.
모든 API는 token을 이용한다. 그래서 api/token/
에서 아이디와 패스워드를 이용하여 jwt 토큰을 부여 받는다.
의문인건 그럼 그 전에 회원가입을 해야하는데 token이 필요하다는 것이었다. (철학자의 원탁?)
찾아보니 view 마다 권한을 따로 설정할 수 있으며 default 권한은 setting에서 설정하는 것임을 알게 되었다.
다음과 같이 회원가입에 따로 권한을 설정했다.
class UserSignupView(APIView):
permission_classes = [AllowAny]
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request):
serializer = UserSerializer(data=request.data)
data = {}
if serializer.is_valid():
user = serializer.save()
data['user_nickname'] = serializer.data['user_nickname']
else:
data = serializer.errors
return Response(data, status=status.HTTP_400_BAD_REQUEST)
return Response(data)
회원가입할 때 JWT를 발행할 수 있지만 회원가입 후 로그인하는 것으로 만들기 위해 따로 토큰 발행을 하지 않았다.