drf之jwt
一、cookie、session、token简介
# 无cookie时期 很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器,
不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议,
就是请求加响应, 尤其是我不用记住是谁刚刚发了HTTP请求, 每个请求对我来说都是全新的。这段时间很嗨皮 # cookie+session认证时期 但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品,
也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,所以想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样,
每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了 # cookie是保存到客户端上以K:V键值对形式保存的 # session是保存到服务端上的 K是随机字符串V是用户信息,然后通过sessionid获取, sessionid保存到cookie中
# 服务端保存sessionid会有压力 所以出现了token验证
token: 三段式 第一段:头:公司信息,加密方式。。。 {} 第二段:荷载:真正的数据 {name:lqz,id:1} 第三段:签名,通过第一段和第二段,通过某种加密方式加密得到的 aafasdfas 加密的时候肯定是要加盐处理的 token的使用分两个阶段 -登录成功后的【签发token阶段】---》生成三段 -登录成功访问某个接口的 【验证阶段】---》验证token是否合法 # token是三段式,服务端生成的,存放在客户端(浏览器就放在cookie中,移动端:存在移动端中) 使用token的认证机制,服务端还要存数据吗? token是服务端的生成,客户端保存,服务端的不存储token
更多知识点:https://www.cnblogs.com/liuqingzheng/p/8990027.html
二、jwt的原理介绍
# jwt(Jason Web Token), Token应用于web方向称之为jwt # 构成:有头、荷载、签名构成 -其实就是一段字符串,有三部分信息构成,然后将这个三部分用.链接到一起就构成了jwt字符串,eg: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ # heard:头 -声明类型:jwt -声明加密的算法, 通常直接使用 HMAC SHA256 ,也可以不用声明 -公司信息 {'typ':'jwt', 'alg':'HS256'...} 然后通过base64的编码就会变成一段字符串 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 # payload:荷载 -exp: jwt的过期时间,这个过期时间必须要大于签发时间 -iat: jwt的签发时间 - 用户信息: 用户信息 {'exp':'123456', 'name':'lqz', 'id',2} 然后通过base64的编码就会变成一段字符串 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 # signature:签名 把头和荷载编码之后 在将它们两个加密后得到: TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ # 注意:secret是保存在服务器端的(加密方式+盐),jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了 # jwt最核心的就是 -签发:登入接口签发 -认证:认证类认证
1.session的认证机制
2.jwt的认证机制
jwt认证机制是最快的 因为jwt是直接在代码中直接签发和认证不用去数据库中读取
三、base64的编码与解码
# 我们在签发和验证token的时候 都需要把密文给编码和解码这样就会相对安全的 所以现在我们可以来学学base的编码和解码 # base64简介 base64可以把字符串编码成base64的编码格式(字母,数字,=) eg: eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9 # base64可以把base64编码的字符串,解码回原来的格式 # 应用场景: - jwt中使用 - 网络中传输字符串就可以使用base64编码 - 网络中传输图片,也可能使用base64的编码
1.代码演示
1.1编码
import base64 import json d = {'name': 'jason', 'password': 123} res = json.dumps(d) print(res) info = base64.b64encode(res.encode('utf8')) print(info) # eyJuYW1lIjogImphc29uIiwgInBhc3N3b3JkIjogMTIzfQ== # 这样就是把json格式的字符串编码成了base的编码格式
1.2 解码
info_decode = base64.b64decode(info) print(info_decode) # b'{"name": "jason", "password": 123}' d1 = json.loads(info_decode) # 再通过json转换成python的字典即可 print(d1) # {'name': 'jason', 'password': 123}
四、drf-jwt的快速使用
# jwt 签发(登入接口)和认证(认证) # django中使用drf需要下载 -https://github.com/jpadilla/django-rest-framework-jwt (比较老) -https://github.com/jazzband/djangorestframework-simplejwt (比较新) -也可以自己写 # 安装 pip3 install djangorestframework-jwt # 安装号之后我们就可以使用了 # 快速使用 # 1.因为这个模块是配合django自带的user表一起使用的 所以要先迁移表 # 2.创建用户 # 3.因为只有登入用户才能签发token,所以我们要编写一个登入接口 但是这个模块已经帮我们写好了所以不需要写 # 4.我们只需要在路径上配置好即可
1.代码演示
1.1 签发
# url.py from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('login/', obtain_jwt_token), ] # 这样token的签发就写好了 # 我们在前端访问的时候只需要访问 /login/接口的时候就会返回token
1.2 认证
# 当token签发号之后 我们就可以带着这个token然后访问某个接口即可 然后校验这个token如果通过才能让这个用户访问 # url: from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('user/', views.UserView.as_view()), ] # view from rest_framework.views import APIView from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response class UserView(APIView): authentication_classes = [JSONWebTokenAuthentication, ] permission_classes = [IsAuthenticated, ] def get(self, request): return Response('ok')
五、修改jwt返回格式
# jwt返回格式只返回了一个token这样不符合我们的返回格式 所以我们需要重写返回格式 # 其实读了源码我们可以知道jwt返回token也是在jwt的配置文件中配置了 # 所以我们只需要自己写一个函数 然后在配置文件中配置即可
1.代码演示
# url from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('login/', obtain_jwt_token), ] # 函数 def jwt(token, user=None, request=None): return { 'code': 100, 'msg': '登入成功', 'username': user.username, 'id': user.id, 'token': token, } # 配置文件 JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt.jwt', }
六、自定义user表,签发token
# 向上面的第三方模块是需要跟自带的use表配合使用 # 但是我们也可以自己自定义user表签发token # 首先我们要创建一个用户表,并创建一个用户 class User(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) # 然后写一个登录接口 from .models import User from rest_framework_jwt.settings import api_settings from rest_framework.response import Response from rest_framework.exceptions import APIException jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER class LoginView(APIView): def post(self, request): try: username = request.data.get('username') password = request.data.get('password') user = User.objects.get(username=username, password=password) payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) # token和payload是从源码中看来的 return Response({ 'code': 100, 'msg': ' 登入成功', 'username': username, 'id': user.id, 'token': token }) except Exception as e: # 这里可以有很多错误 不过我统一变成了错误,其实还可以捕获 用户没写什么的 raise APIException('用户名获密码错误') # 路由 urlpatterns = [ path('login/', views.LoginView.as_view()) ]
1.jwt源码分析
# 我们可以知道jwt帮我写了token的签发和认证 那么我们其实可以看一下它是怎么写的 # 首先入口肯定是路由匹配 from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('login/', obtain_jwt_token), ] # 点进去看的时候发现是一个函数的内存地址 obtain_jwt_token = ObtainJSONWebToken.as_view() # 那么我们就看一下ObtainJSONWebToken这个怎么写的 class ObtainJSONWebToken(JSONWebTokenAPIView): serializer_class = JSONWebTokenSerializer # ObtainJSONWebToken就是写了一个序列化类 然后继承了JSONWebTokenAPIView def post(self, request, *args, **kwargs) # 就是写了一个post方法 serializer = self.get_serializer(data=request.data) if serializer.is_valid(): # 验证用户名和密码是否正确 user = serializer.object.get('user') or request.user token = serializer.object.get('token') # 正确 就产生一个token 然后token是从序列化类中产生的 所以我们可以看一下这个序列化类 response_data = jwt_response_payload_handler(token, user, request) response = Response(response_data) if api_settings.JWT_AUTH_COOKIE: expiration = (datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA) response.set_cookie(api_settings.JWT_AUTH_COOKIE, # 然后将token在和用户信息保存到cookie中 token, expires=expiration, httponly=True) return response # 返回出去 return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # 序列化类就是这个JSONWebTokenSerializer def validate(self, attrs): if user: if not user.is_active: msg = _('User account is disabled.') raise serializers.ValidationError(msg) payload = jwt_payload_handler(user) # 这个就是根据用户产生荷载 return { 'token': jwt_encode_handler(payload), # 然后根据荷载产生token 'user': user } # 然后这两个参数就是上面已经搭配好了 jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER # 在jwt的配置文件中是可以找到的 DEFAULTS = { 'JWT_ENCODE_HANDLER': 'rest_framework_jwt.utils.jwt_encode_handler', 'JWT_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_payload_handler', } # 所以就是这里让我们自己写的时候借助了jwt的产生token
作业
1.自定义认证类,验证token
from rest_framework_jwt.settings import api_settings from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from .models import User import jwt jwt_decode_handler = api_settings.JWT_DECODE_HANDLER class TokenAuthentication(BaseAuthentication): def authenticate(self, request): jwt_value = request.META.get('HTTP_TOKEN') # 这个token是我们自己签发的token if jwt_value is None: return None try: payload = jwt_decode_handler(jwt_value) print(payload) except jwt.ExpiredSignature: msg = ('token过期了 ') raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = ('token解析错误') raise exceptions.AuthenticationFailed(msg) except jwt.InvalidTokenError: raise exceptions.AuthenticationFailed('未知错误') user = User.objects.filter(pk=payload['user_id']).first() return user, jwt_value
view.py
class UsersView(APIView): authentication_classes = [TokenAuthentication, ] def get(self, request): return Response('ok')