- 一、Token与Session
- 1. Session的弊端
- 2. Token认证机制
- 二、JWT的概念及构成
- 三、Djangorestframework-simplejwt
- 1. 安装与配置
- 2. 基本使用方法
- 3. 常用配置
- 4. 自定义生成的Token内容
- 5. JWT解码
我们之前已经学过session,它是将用户的敏感信息保存到服务端,而只给客户端一个sessionid(保存为cookie)作为与服务器端session交互的凭证。在用户通过验证并拿到sessionid后,几乎每次访问都需要携带sessionid,并需要服务端每次都从数据库中获取session信息。这就暴露了如下的弊端:
-
需要频繁查询数据库中的session,服务器压力大。
-
session是基于cookie的,如果cookie被截获,容易遭到CSRF攻击。
-
session被保存在用户第一次访问的服务器中(一般在内存中),如果有多个服务器,其他服务器无法获取到这些session信息,所以扩展性差。
-
用户通过验证后,由服务端对数据进行编码;
-
将加密后的数据保存到客户端,称为Token:由js进行读写,保存在Local Storage中;
-
以后再访问服务端时,附带Token,服务端进行验证、解码。
如此,便减小了服务端的压力,由于不依赖于cookie,所以无需担心CSRF攻击。并且,Token由客户端发送,不依赖于某一台服务器,所以扩展性很好。
二、JWT的概念及构成JWT的全称为JSON Web Token,它是一个开放的标准,定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。
JWT的本质就是一个字符串,它将用户信息保存到JSON字符串中,然后进行BSEA64编码,并附带一个签名(Signature),防止信息被篡改。
JWT看起来就是下面这样,因为是编码后的,所以是一堆乱七八糟的字符:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
它由三部分构成:
-
Header(头部):通常包含token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)以及其他信息。
-
Payload(载荷):是JWT的主体,通常包含用户主键、用户名、签发时客户信息(设备号、地址)、过期时间等。
注意:因为base64是可逆的,所以不要在JWT的payload或header中放置敏感信息,除非它们是加密的!
-
Signature(签名):是对上面两部分编码后的内容再进行加密后得到的密文。即:base64(Header)+"."+base64(Payload)通过SHA256加密后,得到的密文就是签名。
最后,将上面三部分通过.连接起来就得到了JWT。
三、Djangorestframework-simplejwtDjangorestframework-simplejwt是DRF(django rest framework)的一个JWT认证插件,为DRF提供了一个JWT认证后端。
1. 安装与配置-
使用pip安装:
pip install djangorestframework-simplejwt
-
全局配置:
在项目配置文件settings.py中,将JWTAuthentication添加到认证后端列表:
REST_frameWORK = { ... 'DEFAULT_AUTHENTICATION_CLASSES': ( ... 'rest_framework_simplejwt.authentication.JWTAuthentication', ) ... } -
局部配置:
或者,仅在需要的视图中配置:
class UserCenterView(GenericAPIView): authentication_classes = [JWTAuthentication] …… -
添加路由:
from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView) urlpatterns = [ # 获取token用的路由 path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # 刷新token用的路由 path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh') ] -
测试:
创建django超级用户:
python manage.py createsuperuser
使用浏览器访问路径api/token/,会显示以下界面:
输入用户名和密码点击POST后,验证通过就会返回token:
说明:
- access是token的主体;
- refresh是用来刷新token的,执行token刷新操作时,只会返回一个新的access。
在之前配置的基础上(此处默认为全局),向视图写入权限等其他配置:
class UserAPIView(GenericAPIView):
"""
测试的Api1
"""
# 全局设置了认证后端,不再进行局部设置
# 设置权限认证,比如仅允许认证后的用户访问
permission_classes = [permissions.IsAuthenticated]
queryset = User.objects.all()
serializer_class = UserSerializer
def get(self, request):
return Response("只有登录后的用户才能访问哦")
然后进行token的获取:
运行项目,使用POST方法请求路径api/token/,并在body中附带username和password信息。即可得到access。
最后使用GET方法访问UserAPIView视图,分为两种情况:
- 错误情况:直接访问,没有附带Token,则会返回“身份认证信息未提供”的提示信息。
- 正确情况:将token添加到请求头的Authorization中,就会成功返回“只有登录后的用户才能访问哦”。
可以在项目配置文件中,对jwt进行配置:
# 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': 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',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
-
ACCESS_TOKEN_LIFETIME:
access token的有效时长,必须是一个datetime.timedelta对象。
-
REFRESH_TOKEN_LIFETIME:
refresh token的有效时长,必须是一个datetime.timedelta对象。
-
ROTATE_REFRESH_TOKENS:
刷新access token时,是否刷新refresh token。
-
UPDATE_LAST_LOGIN:
当设置为True时,用户进行登录时,会在自动更新auth_user表的last_login字段。
-
ALGORITHM:
生成签名部分的算法,可以使用’HS256’(默认), ‘HS384’, ‘HS512’。
-
AUTH_HEADER_NAME:
用于身份验证的authorization头名称,默认为HTTP_AUTHORIZATION。
比如获取authorization头的内容:
class ExampleView(GenericAPIView): ... def get(self, request): token = request.meta.get("HTTP_AUTHORIZATION")
Djangorestframework-simplejwt也是通过序列化器来序列化和反序列化JWT的,所以我们可以通过自定义序列化器的方式,定制token。
在定义序列化器的文件中:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# 添加自定义内容
token['name'] = user.name
……
return token
在视图文件中:
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import *
class MyTokenObtainPairView(TokenObtainPairView):
# 设置为自定义的token序列化器
serializer_class = MyTokenObtainPairSerializer
最后,与标准token视图一样,配置好url即可。
注意:上面的修改会同时作用于获取Token和刷新Token操作。
5. JWT解码在定义序列化器的文件中:
from rest_framework_simplejwt.serializers import TokenVerifySerializer
from jwt import decode as jwt_decode
class MyTokenVerifySerializer(TokenVerifySerializer):
def validate(self, attrs):
"""
attrs['token']: 是请求的token
settings.SECRET_KEY: setting.py默认的key 除非在配置文件中修改了
algorithms: 加密的方法
"""
decoded_data = jwt_decode(attrs['token'], settings.SECRET_KEY, algorithms=["HS256"])
return decoded_data
在视图中:
from rest_framework_simplejwt.views import TokenObtainPairView, TokenViewbase
class MyTokenVerifyView(TokenViewbase):
"""
验证token得到用户信息 token: 验证的token
"""
serializer_class = MyTokenVerifySerializer
之后,访问该视图就会得到解码后的内容了。
当然,我们也可以通过python自带的base64库手动解码。



