day89
1 jwt认证介绍
2 base64介绍和使用
3 jwt基本使用(jwt内置,控制用户登录后能访问和未登陆能访问)
4 控制登录接口返回的数据格式
5 自定义基于jwt的认证类
## 4 jwt认证介绍 ```python 1 不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制,用户登录认证 2 用户只要登录了,返回用户一个token串(随机字符串),每次用户发请求,需要携带这个串过来,验证通过,我们认为用户登录了 3 JWT的构成(字符串) -三部分(每一部分中间通过.分割):header payload signature -header:声明类型,这里是jwt,声明加密算法,头里加入公司信息...,base64转码 { 'typ': 'JWT', 'alg': 'HS256' } -payload:荷载(有用),当前用户的信息(用户名,id,这个token的过期时间,手机号),base64转码 { "sub": "1234567898", "name": "egon", "admin": true, "userid":1, 'mobile':123444444 } -signature:签名 -把前面两部分的内容通过加密算法+密钥加密后得到的一个字符串 -jwt总的构成样子: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 4 JWT认证原理 -用户携带用户名,密码登录我的系统,校验通过,生成一个token(三部分),返回给用户---》登录功能完成 -访问需要登录的接口(用户中心),必须携带token过来,后端拿到token后,把header和payload截出来,再通过一样的加密方式和密码得到一个signature,和该token的signature比较,如果一样,表示是正常的token,就可以继续往后访问 ``` ## 5 base64介绍和使用 ```python 1 任何语言都有base64的加码和解码,转码方式(加密方式) 2 python中base64的加密与解密 import base64 import json dic_info={ "name": "lqz", "age": 18 } # 转成json格式字符串 dic_str=json.dumps(dic_info) print(dic_str) #eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0= #eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0= # 需要用bytes格式 # 加密 base64_str=base64.b64encode(dic_str.encode('utf-8')) print(base64_str) # 解密 res_bytes=base64.b64decode('eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=') print(res_bytes) ``` ## 6 jwt基本使用(jwt内置,控制用户登录后能访问和未登陆能访问) ```python 1 drf中使用jwt,借助第三方https://github.com/jpadilla/django-rest-framework-jwt 2 pip3 install djangorestframework-jwt 3 快速使用(默认使用auth的user表) 1 在默认auth的user表中创建一个用户 2 在路由中配置 path('login/', obtain_jwt_token), 3 用postman向这个地址发送post请求,携带用户名,密码,登陆成功就会返回token 4 obtain_jwt_token本质也是一个视图类,继承了APIView -通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回 -如果校验失败,返回错误信息 4 用户登录以后才能访问某个接口 -jwt模块内置了认证类,拿过来局部配置就可以 -class OrderView(APIView): # 只配它不行,不管是否登录,都能访问,需要搭配一个内置权限类 authentication_classes = [JSONWebTokenAuthentication, ] permission_classes = [IsAuthenticated,] def get(self, request): print(request.user.username) return Response('订单的数据') 5 用户未登录,不能访问 -class OrderView(APIView): # 只配它不行,不管是否登录,都能访问,需要搭配一个内置权限类 authentication_classes = [JSONWebTokenAuthentication, ] def get(self, request): print(request.user.username) return Response('订单的数据') 6 如果用户携带了token,并且配置了JSONWebTokenAuthentication,从request.user就能拿到当前登录用户(request.user.username),如果没有携带,当前登录用户就是匿名用户(request.user.username=None) 7 前端要发送请求,携带jwt,格式必须如下 -把token放到请求头中,key为:Authorization -value必须为:jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1LCJ1c2VybmFtZSI6ImVnb24xIiwiZXhwIjoxNjA1MjQxMDQzLCJlbWFpbCI6IiJ9.7Y3PQM0imuSBc8CUe_h-Oj-2stdyzXb_U-TEw-F82WE ``` ## 7 控制登录接口返回的数据格式 ```python 1 控制登录接口返回的数据格式如下 { code:100 msg:登录成功 token:asdfasfd username:egon } 2 写一个函数 from homework.serializer import UserReadOnlyModelSerializer def jwt_response_payload_handler(token, user=None, request=None): return {'code': 100, 'msg': '登录成功', 'token': token, 'user': UserReadOnlyModelSerializer(instance=user).data } 3 在setting.py中配置 import datetime JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler', } ``` ## 8 自定义基于jwt的认证类 ```python 1 自己实现基于jwt的认证类,通过认证,才能继续访问,通不过认证就返回错误 2 代码如下 class JwtAuthentication(BaseJSONWebTokenAuthentication): def authenticate(self, request): # 认证逻辑() # token信息可以放在请求头中,请求地址中 # key值可以随意叫 # token=request.GET.get('token') token=request.META.get('HTTP_Authorization'.upper()) # 校验token是否合法 try: payload = jwt_decode_handler(token) except jwt.ExpiredSignature: raise AuthenticationFailed('过期了') except jwt.DecodeError: raise AuthenticationFailed('解码错误') except jwt.InvalidTokenError: raise AuthenticationFailed('不合法的token') user=self.authenticate_credentials(payload) return (user, token) 3 在视图类中配置 authentication_classes = [JwtAuthentication, ] ```
settings.py
import datetime JWT_AUTH = { # 过期时间 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 自定义认证结果:见下方序列化user和自定义response # 如果不自定义,返回的格式是固定的,只有token字段 'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler', }
urls.py
from django.contrib import admin from django.urls import path from app01 import views from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('admin/', admin.site.urls), path('login/', obtain_jwt_token), path('order/', views.OrderView.as_view()), ]
serializer.py
from rest_framework import serializers from app01.models import UserInfo from rest_framework.exceptions import ValidationError class UserModelSerializer(serializers.ModelSerializer): # 注册功能,需要什么字段? # username,password,re_password,mobile # 这个字段在表中没有,咱们写成write_only=True re_password = serializers.CharField(max_length=18, min_length=3, required=True, write_only=True) class Meta: model = UserInfo fields = ['username', 'password', 'mobile', 're_password', 'icon'] extra_kwargs = { 'username': {'max_length': 12, 'min_length': 3}, 'password': {'write_only': True}, 'icon': {'read_only': True} } # 写mobile的局部钩子 def validate_mobile(self, data): if len(data) == 11: return data else: raise ValidationError('手机号不合法') # 全局钩子校验两次密码是否一致 def validate(self, attrs): password = attrs.get('password') re_password = attrs.get('re_password') if password == re_password: # re_password不存数据库,剔除 attrs.pop('re_password') return attrs else: raise ValidationError('两次密码不一致') ## 重写create方法,实现密码的加密 def create(self, validated_data): # re_password也可也在这里移除 # UserInfo.objects.create(**validated_data) # 密码是明文 user = UserInfo.objects.create_user(**validated_data) return user # 不要忘了这句话 # 序列化的时候 # 一个模型类,不一定只对着一个序列化类 # 只做序列化用 class UserReadOnlyModelSerializer(serializers.ModelSerializer): class Meta: model = UserInfo fields = ['username', 'mobile', 'icon', 'email', 'date_joined'] # 这个序列化类,只做修改头像用 class UserIconModelSerializer(serializers.ModelSerializer): class Meta: model = UserInfo fields = ['icon', ]
utils.py
from rest_framework.response import Response from app01.serializer import UserReadOnlyModelSerializer def jwt_response_payload_handler(token, user=None, request=None): # 参数token,user,request return {'code': 100, 'msg': '成功', 'token': token, 'user': UserReadOnlyModelSerializer(instance=user).data}
my_jwtauth.py
# 自定义认证类 import jwt from rest_framework_jwt.utils import jwt_decode_handler from rest_framework_jwt.authentication import JSONWebTokenAuthentication, BaseJSONWebTokenAuthentication from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication class MyJwtAuthentication(BaseJSONWebTokenAuthentication): def authenticate(self, request): token = request.META.get('HTTP_AUTHORIZATION') try: payload = jwt_decode_handler(token) except jwt.ExpiredSignature: msg = 'Signature has expired.' raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = 'Error decoding signature.' raise exceptions.AuthenticationFailed(msg) except jwt.InvalidTokenError: raise exceptions.AuthenticationFailed('未知错误') user = self.authenticate_credentials(payload) return (user, token)
views.py
from django.shortcuts import render # Create your views here. from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated # class OrderView(APIView): # authentication_classes = [JSONWebTokenAuthentication, ] # permission_classes = [IsAuthenticated, ] # # def get(self, response): # return Response('订单') from app01 import my_jwtauth class OrderView(APIView): authentication_classes = [my_jwtauth.MyJwtAuthentication, ] # 使用自定义认证类 def get(self, response): return Response('订单')

浙公网安备 33010602011771号