💡중요!
- session 인증은 모든 유저의 정보와 session 정보를 서버에서 관리
- JWT 인증에서는 토큰에 유저의 정보를 담는다.(서버에 인증정보 저장x)
: Django에서 기본적으로 제공하는 session 인증을 사용해보면 django_session 테이블이 생성되고 그 안에 session_key, session_data, expire_date와 같은 필드로 session 정보가 저장됩니다.
💡중요! session 정보를 서버에서 관리
: 클라이언트도 브라우저에 session id를 저장하여 사용하지만 session id 자체는 중요한 정보가 담겨있지 않은 일종의 임시 비밀번호이며 실제 session의 정보를 관리하는 것은 전적으로 서버의 역할
💡중요! session 인증의 동작과정과의 차이점
- 3번 과정에서 session 정보가 서버에 저장되는 반면 JWT 인증에서는 서버에 아무것도 저장되지 않는다.
- 5번 과정에서 session은 확인을 위해 데이터 베이스를 탐색하지만, JWT 인증에서는 토큰을 바로 검증.
(DB 탐색과정 필요없으므로 -> 별도의 테이블 생성하지 않아도 됨
Why? 토큰 안에 유저정보가 담겨있어서, 별도의 데이터 및 저장 공간이 필요하지 않음.)
ex) JSON 정보는 Base64Url로 인코딩 되어 JWT의 첫 번째 영역이 된다.
{
"typ": "JWT",
"alg": "HS256"
}
ex) payload 역시 Base64Url로 인코딩 되어 JWT의 두 번째 영역이 된다.
{
"token_type": "access",
"exp": 1649145719,
"jti": "1foo2jwt3id4",
"user_id": 123
}
잠깐)
Payload영역에 담긴 유저 정보는 인코딩만 되어 있고 별도의 암호화 처리가 되어있지 않다. (= 쉽게 디코딩 될 수 있고 변조 될 수 있다.)
but, payload에는 유저를 특정할 수 있는 user id만 담았기 때문에 토큰이 디코딩 되어 user id가 노출되는 것은 보안상 큰 문제가 X
So, 변조의 문제만 해결하면 되고, 그 문제를 signature 영역에서 해결하는 것!!
- Simple JWT에서는 secret으로 Django 프로젝트마다 사용하는 secret_key를 기본으로 이용.
-> setting.py에서 확인 가능!
마침내 토큰이 검증되면 서버는 payload에 담긴 유저의 정보를 통해 유저를 특정하고 인증된 유저로 처리합니다.
그렇기 때문에 JWT 인증은 별도의 데이터베이스 탐색 없이도 빠르게 유저 인증을 수행할 수 있습니다
python -m pip install djangorestframework-simplejwt
from datetime import timedelta
INSTALLED_APPS = [
...
# simple-jwt 추가해주기
'rest_framework_simplejwt',
]
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
# 추가적인 JWT_AUTH 설정
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(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
SIMPLE_JWT 기타설정
https://velog.io/@auddwd19/DRF-simpleJWT-%EA%B8%B0%ED%83%80%EC%84%A4%EC%A0%95
-> 토큰에 담긴 사용자의 정보를 의미하는 claim 을 커스터마이징
-> Serializer 를 활용하여 simplejwt 에서 제공하는 기본 정보 이외에 우리가 포함하고 싶은 정보를 토큰에 추가적으로 넣기 가능.(우리가 클라이언트단에서 입력할 ID/PW)
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['username'] = user.username
# ...
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
참조
from .views import MyTokenObtainPairView
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
...
path('token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
http://127.0.0.1:8000/api/token/ 접속 시 다음과 같은 화면 출력
createsuperuser로 아이디,비번 생성 후 post하면 refresh token과 access token을 확인할 수 있다.
refresh token을 http://127.0.0.1:8000/api/token/refresh/로 이동하여 토큰을 입력하면 새로운 access token을 받게 된다.
refresh된 토큰 이전의 토큰을 붙여넣게 되면 블랙리스트의 보안으로 다음과 같이 토큰이 출력되지 않는다.
JWT 의 주요한 이점은 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요없다는 것이다. 분산 마이크로 서비스 환경에서 중앙 집중식 인증 서버와 데이터베이스에 의존하지 않는 쉬운 인증 및 인가 방법을 제공한다. 개별 마이크로 서비스에는 토큰 검증과 검증에 필요한 비밀 키를 처리하기위한 미들웨어가 필요하다. 검증은 서명 및 클레임과 같은 몇 가지 매개 변수를 검사하는 것과 토큰이 만료되는 경우로 구성된다.토큰이 올바르게 서명되었는지 확인하는 것은 CPU 사이클을 필요로하며 IO 또는 네트워크 액세스가 필요하지 않으며 최신 웹 서버 하드웨어에서 확장하기가 쉽다.
JWT 사용을 권장하는 몇 가지 이유는 다음과 같다.
참조: http://www.opennaru.com/opennaru-blog/jwt-json-web-token/
💡중요!
Access Token은 발급 이후에 서버에 저장되지 않고 토큰 자체로 검증을 하며 사용자 권한을 인증하기 때문에 탈취 당한다면 토큰이 만료되기 전까지는 누구나 권한 접근이 가능해져버리는 것이다.
So, JWT를 발급하고 삭제하는 것은 불가능하기 때문에, 대신 토큰 유효시간을 짧게하여 토큰 남용을 방지하는 것이 해결책이 될 수 있다.
But, 유효기간이 짧은 토큰의 경우 사용자가 그만큼 로그인을 자주해서 새롭게 토큰을 발급받아야한다.
Finally, 유효기간을 짧게 하면서 보안을 강화할 수 있는 방법은 없을까? -> Refresh Token
중요!
1. 서버는 로그인 성공 시 클라이언트에게 Access Token과 Refresh Token을 동시에 발급
2. 서버는 DB에 Refresh Token을 저장, 클라이언트는 Access Token과 Refresh Token을 쿠키,또는 로컬스토리지에 저장하고 요청이 있을 때마다 헤더에 담아서 보낸다.
3. 만료된 Access Token을 서버에 보내면, 서버는 같이 보내진 Refresh Token을 DB에 있는 것과 비교해서 일치하면 다시 Access Token을 재발급하는 원리
4. 사용자가 로그아웃을 하면 저장소에서 Refresh Token을 삭제하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서 다시 재발급해서 DB에 저장
잠깐) 토큰 유효시간 설정
- setting.py에서 설정 가능!
SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), 'REFRESH_TOKEN_LIFETIME': timedelta(days=90), ...
참조: https://inpa.tistory.com/entry/WEB-📚-Access-Token-Refresh-Token-원리-feat-JWT [👨💻 Dev Scroll:티스토리]
앞서 살펴본 refresh token으로 보안적인 문제를 해결할 수 있었지만, refresh token이 탈취될 수도 있는 보안적인 이슈가 존재한다.
-> 정해져 있는 유효기간이 끝나지 않는 이상 토큰방식은 토큰을 강제로 만료시킬 수 없는 방식
So, 유저의 로그아웃을 통해 더이상 필요없는 토큰이나 악의적으로 탈취된 token을 서버에서 사용할 수 없도록 관리를 해주는 방법(= refresh token이 blacklist 됨)
<settings.py>
INSTALLED_APPS = [
...
'rest_framework_simplejwt.token_blacklist',
]
SIMPLE_JWT = {
...
'BLACKLIST_AFTER_ROTATION': True,
...
}
참조
블랙리스트에 대한 설명
참고: 진짜 잘 정리된 사이트
JWT란?https://inpa.tistory.com/559#JWT%EC%9D%98_Access_Token_/_Refresh_Token_%EB%B0%A9%EC%8B%9D
Access& Refresh Token
https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Access-Token-Refresh-Token-%EC%9B%90%EB%A6%AC-feat-JWT참조
코드 구현
https://www.youtube.com/watch?v=xjMP0hspNLE
JWT
https://www.qu3vipon.com/django-jwt
Django BE로 사이드프로젝트 백엔드 구성중이었는데 jwt 구현에 대해서 잘 읽고갑니다!!