session도 역시 JWT와 비슷하게 서버에서 session ID라는 것을 발급하고 authorization에 이용한다. 하지만, session은 검증을 위한 정보를 서버의 DB에 저장하고 있기 때문에 DB에 빈번하게 요청을 해야한다. JWT는 검증을 위해 별도로 DB에 요청하지 않는다. 클라이언트가 보내는 JWT에 정보가 이미 다 있기 때문에 서버는 자신이 가진 검증 키를 이용하기만 하면 된다. 그랩의 블로그의 그림을 보면 무슨 의미인지 알 것이다.
다른 프레임 워크는 모르겠으나 drf(django restframework) 같은 경우 기본적으로 제공하는 TokenAuthentication이 있다. 해당 인증 역시 토큰 기반인 것은 JWT와 같다. 하지만, expiration이 존재하지 않는다. 또한 검증을 위해 DB에 접근해야한다.
JWT 기반이면서 DRF built-in token에 비해 보안이 좋아 많이 사용되고 있다. (DRF built-in token은 상대적으로 문자열 구성이 단순하기 때문)
자료 출처: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html
# Django project settings.py from datetime import timedelta ... SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5), "REFRESH_TOKEN_LIFETIME": timedelta(days=1), "ROTATE_REFRESH_TOKENS": False, "BLACKLIST_AFTER_ROTATION": False, "UPDATE_LAST_LOGIN": False, "ALGORITHM": "HS256", "SIGNING_KEY": settings.SECRET_KEY, "VERIFYING_KEY": "", "AUDIENCE": None, "ISSUER": None, "JSON_ENCODER": 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), "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer", "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer", "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer", "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer", "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer", "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", }
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": False,
timedelta : 다른 datetime 객체 간의 사이/차이를 나타내주는 모듈이다.
ACCESS_TOKEN_LIFETIME : timedelta를 사용해 괄호 안의 유효기간의 default를 설정해, access token의 유효 기간을 정해준다.
REFRESH_TOKEN_LIFETIME : timedelta를 사용해 괄호 안의 유효기간의 default를 설정해, refresh token의 유효 기간을 정해준다.
ROTATE_REFRESH_TOKENS : True로 설정될 시, refresh token이 TokenRefreshView에 제출되면, 새로운 refresh token이 새로운 access token과 함께 반환될 것이다. 새로운 refresh token은 JSON response로 refesh key에 의해 공급될 예정이다. 새로운 refresh token들은 REFRESH_TOKEN_LIFETIME에서 정의된 대로 더 늘어난 갱신된 유효기간을 갖게 될 것이다. BLACKLIST_AFTER_ROTATION 세팅이 True라면, refresh view로 제출된 refresh token들은 blacklist에 추가될 것이다.
BLACKLIST_AFTER_ROTATION: Ture로 설정될 시, 블랙리스트 앱이 사용 중에 있고, ROTATE_REFRESH_TOKENS가 True로 설정되어 있다면, TokenRefreshView로 제출된 refresh token들이 블랙리스트에 추가된다. blacklist 를 사용하고 싶다면, INSTALLED_APPS에 'rest_framework_simplejwt.token_blacklist'를 추가해야 한다.
UPDATE_LAST_LOGIN : True로 설정될 시, auth_user 테이블에 있는 last_login(마지막 로그인된 시각)이라는 필드가 TokenObtainPairView를 통해 로그인이 된다면, update된다. DB에서 처리할 것들이 많아져 서버가 느려지고, 보안을 취약하게 할 수 있으므로 사용에 주의할 것
"ALGORITHM": "HS256",
"SIGNING_KEY": settings.SECRET_KEY,
"VERIFYING_KEY": "",
"AUDIENCE": None,
"ISSUER": None,
"JSON_ENCODER": None,
"JWK_URL": None,
"LEEWAY": 0,
"ALGORITHM" : PyJWT library에서 나온 알고리즘으로, signing/verification operations on tokens을 수행하기 위해 쓰인다. symmetric HMAC signing and verification을 사용하기 위해선, 'HS256', 'HS384', 'HS512'의 알고리즘들이 쓰인다. HMAC algorithm을 사용하게 되면, SIGNING_KEY 설정은 signing key(서명 키)와 the verifying key(확인 키)로 모두 사용된다. 이 경우엔 VERIFYING_KEY 설정은 무시된다.
반면, asymmetric RSA signing and verification을 사용하기 위해선, 'RS256', 'RS384', 'RS512'의 알고리즘이 사용된다. RSA algorithm이 선택되면, SIGNING_KEY 설정이 RSA 퍼블릭 키를 포함한 문자열로 설정되어야 한다. 마찬가지로 VERIFYING_KEY 설정 역시 RSA 퍼블릭 키를 포함한 문자열로 설정되어야 한다.
"SIGNING_KEY" : 생성된 토큰의 콘텐츠에 서명하는 데 사용되는 서명 키이다.
SIGNING_KEY는 SECRET_KEY의 value값을 기본으로 설정된다.SIGNING_KEY를 SECRET_KEY로 바꾸지 않고 다른 값으로 바꾸면, 타협이 된 이벤트 안에서 더 쉽게 토큰을 signing key로 사용할 수 있게 한다."VERIFYING_KEY" : 생성된 토큰의 내용을 검증하기 위해 쓰인다.
"AUDIENCE": None, : audience는 생성된 토큰들에 포함되거나 decoding된 토큰에서 검증되었다고 주장한다. None 으로 설정될 시, 이 필드는 토큰으로부터 제외되고, 검증받지 않는다.
"ISSUER": None, : issuer는 생성된 토큰 안에 포함되거나 decoding된 토큰에서 검증되었다고 주장한다. None 으로 설정될 시, 이 필드는 토큰으로부터 제외되고, 검증받지 않는다.
"JWK_URL": None, : JWK_UR는 토큰의 서명을 검증하기 위해 필요한 퍼블릭 키들을 극적으로 해결해주는 데 쓰인다. 예시로, Auth0를 사용하려고 할 때 ‘https://yourdomain.auth0.com/.well-known/jwks.json’.로 설정할 수 있다. None으로 설정 시, 이 필드는 토큰 벡엔드로부터 제외되고, 검증(validation)동안, 사용되지 않는다.
"LEEWAY": 0, : 토큰의 만료기간에 대한 margin(틈/여백)을 줄 때 사용한다. 초를 위한 정수나 datetime.timedelta을 이용해서 쓰인다.
"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",
AUTH_HEADER_TYPES: header에 authorization을 넣을 때 토큰 앞에 붙여야 하는 type이다. Authorization: Bearer <token> 와 같은 포맷이 authentication을 할 때 요구되는데, 이를 지정하는 부분이다. 헤더의 타입을 리스트 혹은 튜플로도 가능하다.(e.g. ('Bearer', 'JWT')). 리스트나 튜플과 같은 방식으로 헤더 타입을 설정하고 인증을 했는데, 실패한다면 컬렉션의 첫번째 항목이 response에서 "WWW-Authenticate" 헤더를 작성하는 데에 사용된다.
Authorization: <type> <credentials>인증 타입
Basic
사용자 아이디와 암호를 Base64로 인코딩한 값을 토큰으로 사용한다. (RFC 7617)Bearer
JWT 혹은 OAuth에 대한 토큰을 사용한다. (RFC 6750)Digest
서버에서 난수 데이터 문자열을 클라이언트에 보낸다. 클라이언트는 사용자 정보와 nonce를 포함하는 해시값을 사용하여 응답한다 (RFC 7616)HOBA
전자 서명 기반 인증 (RFC 7486)Mutual
암호를 이용한 클라이언트-서버 상호 인증 (draft-ietf-httpauth-mutual)AWS4-HMAC-SHA256
AWS 전자 서명 기반 인증 (링크)
위 인증 타입의 종류에서 나타난 것 처럼, bearer는 JWT와 OAuth를 타나내는 인증 타입입니다.
AUTH_HEADER_NAME : authentication을 위해 인증 헤더 네임을 지정한다. request 안에 있는 authorization 헤더를 받아들이는 HTTP_AUTHORIZATION가 기본 설정이다.
USER_ID_FIELD : db에 저장된 user model 필드가 user를 확인하기 위해 생성된 토큰들을 포함시킬 것이다. 이 설정 값은 초기 값이 선택되면 일반적으로 변경되지 않는 필드를 지정하는 것이 좋다.
USER_ID_CLAIM : 생선된 토큰을 user 식별자를 저장하기 위해 선언해주는 것이다.
USER_AUTHENTICATION_RULE : 사용자의 authentication 에 대한 규칙을 선언한다. user가 authenticate하길 허용되는지를 확인하기 위해 호출 가능하다. 유효한 토큰이 처리된 후에 이 규칙은 적용된다. 유저 객체는 콜러블에 인수(argument)로서 전달된다. default rule은 is_active 라는 flag(기, 사령기)는 여전히 True임을 확인하는 것이다. Callable은 boolean으로 반환되어야 하고, authorized라면 True를, 401 status code가 나오면 False를 반환되어야 한다.
TOKEN_TYPE_CLAIM : 토큰의 타입을 정하기 위해 쓰이는 선언 명이다.
TOKEN_USER_CLASS: 검증된 토큰(validated token)으로 지원되는 상태 stateless 사용자 객체다. JWTStatelessUserAuthentication 인증 백엔드에서만 사용된다. 그 value 값은 기본값인 rest_framework_simplejwt.models.TokenUser의 하위 클래스로 dotted(점선) 경로다.
"JTI_CLAIM": "jti",
>
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
>
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
JTI_CLAIM : token이 고유한 식별자를 저장하기 위해 쓰인다. token’s unique identifier은 blacklist app에 있는 취소된 토근들을 식별한다. 어떤 경우에는 그러한 값을 저장하기 위해 기본 "jti" 클레임 외에 다른 클레임을 사용해야 할 수도 있습니다.
SLIDING_TOKEN_LIFETIME : authentication을 증명하기 위해 슬라이딩 토큰이 얼마나 유효한지 지정하는 datetime.timedelta 객체다.
SLIDING_TOKEN_REFRESH_LIFETIME : sliding token을 새로 refresh하는 데에 걸리는 유효 기간을 지정하는 datetime.timedelta 객체다.
SLIDING_TOKEN_REFRESH_EXP_CLAIM : sliding token의 refresh 기간의 유효 기간을 저장하기 위해 쓰이는 claim name이다.
CHECK_REVOKE_TOKEN : 이 필드가 True라면, 시스템은 사용자의 현재 비밀번호의 md5 해시를 JWT 토큰의 페이로드 내 REVOKE_TOKEN_CLAIM 필드에 저장된 값과 비교하여 토큰이 취소되었는지 여부를 확인한다.
REVOKE_TOKEN_CLAIM : user의 해시된 비밀번호를 저장하기 위해 쓰이는 선언 name이다. CHECK_REVOKE_TOKEN 필드가 True 값이라면, REVOKE_TOKEN_CLAIM 필드는 jwt payload에 포함될 것이다.
슬라이딩 토큰은 보안 수준이 낮고 블랙리스트 앱을 사용하는 경우 성능이 떨어지는 대신 토큰 사용자에게 보다 편리한 경험을 제공한다. 슬라이딩 토큰은 만료 claim과 새로 고침 만료 claim을 모두 포함하는 토큰입니다. 슬라이딩 토큰의 만료 claim에 있는 타임스탬프가 통과되지 않은 한, 슬라이딩 토큰을 인증 증명에 사용할 수 있다. 또한 새로 고침(refresh) 만료 클레임의 타임스탬프가 지나지 않은 한, 새로 고침 view에 제출하여 갱신된 만료 클레임으로 다른 copy를 얻을 수도 있다.
AUTH_TOKEN_CLASSES 를 ('rest_framework_simplejwt.tokens.SlidingToken',)로 설정하기from rest_framework_simplejwt.views import (
TokenObtainSlidingView,
TokenRefreshSlidingView,
)
urlpatterns = [
...
path('api/token/', TokenObtainSlidingView.as_view(), name='token_obtain'),
path('api/token/refresh/', TokenRefreshSlidingView.as_view(), name='token_refresh'),
...
]
블랙리스트 앱을 사용할 때는 simple jwt는 매 authenticated 요청에 모든 슬라이딩 토큰들을 유효화한다. 이로 인해 authenticated API view들의 퍼포먼스가 줄어들 수 있다.
슬라이딩 세션은 이러한 Stateless 서비스에서의 보안성을 위해 버려지는 편의성을 잡기 위한 세션 전략입니다. 서비스를 계속 사용하는 유저에게는 만료되기 전에 자동으로 만료 기한을 연장시켜주는 방법입니다. 사용자가 웹 페이지에서 활동하는건 JS단에서 쉽게 이벤트 핸들러로 감지할 수 있기 때문에 이러한 코드들을 통해 사용자의 현재 액션 여부를 지속적으로 체크하고 활동 중이라고 판단되면 세션이 만료 시간이 되기 전에 만료시간을 갱신한 토큰을 다시 받아오는 형태로 만료 시간을 연장할 수 있다.
function updateJWT(){
// JWT를 갱신하는 함수를 하나 만들고...
// 물론 이는 서버로 요청해서 받아와야겠죠.
// 단 너무 잦은 요청이 부담스럽다면 현재 JWT의 유효시간을 보고 갱신해도 됩니다.
// 예를들면.. 만기가 다와갈 때
}
// window 전체에 onmouseenter를 걸어서 updateJWT가 호출되도록 합니다.
// 그러면 사용자가 마우스 액션이 있을 때 JWT를 자동으로 갱신 시켜줄 수 있습니다.
// 비슷하게 키보드도 있겠네요.
window.addEventListener("mouseenter", updateJWT, false);
참고자료
https://gpalektzja.medium.com/jwt%EB%A5%BC-%EC%B0%8D%EB%A8%B9%ED%95%B4%EB%B3%B4%EC%9E%90-2bf26656aa42
https://velog.io/@cada/토근-기반-인증에서-bearer는-무엇일까
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/token_types.html