【一】simple-jwt快速使用
【1】快速使用jwt(json web token)
1 django框架上,第三方的jwt解决方案 django-rest-framework-jwt 老,不更新了 djangorestframework-simplejwt 新的,一直在更新 2 安装 # 基于Django+drf使用 pip3 install djangorestframework-simplejwt 3 快速使用 ---> django内置的auth-user表作为用户表 ---》签发和认证 签发:路由配置--》导入模块 from rest_framework_simplejwt.views import token_obtain_pair urlpatterns = [ path('admin/', admin.site.urls), path('login/', token_obtain_pair), ] 认证: # app01/urls.py from django.urls import path from .views import BookView urlpatterns = [ path('books/',BookView.as_view()), ] # views.py class BookView(APIView): # 写了认证类之后必须要写权限类,下面两个一起 authentication_classes = [JWTTokenUserAuthentication] permission_classes = [IsAuthenticated] # 如果不加权限类,执行逻辑是这样的: # 如果携带了token ---》 就去校验 ---》如果有错就会报错 # 如果没有携带token ---》就不会去校验 ---》所以只配置JWTTokenUserAuthentication---》用户就不会带登录信息,是不做校验的 def get(self, request): return Response('是一本好书') # Bearer空格+token(access)
【2】更新access过程
urlpatterns = [ path('admin/', admin.site.urls), path('login/', token_obtain_pair), # 当access过期的时候后端重定向到refresh,重新生成access path('refresh/',token_refresh), path('api/v1/', include('app01.urls')), ]
【3】双token认证
双token认证的目的:尽可能保证token的安全,让access的过期的时间短一些 一个token时间很久 比如7天 refresh 作用:刷新access 一个token时间很短 比如3分钟 access 只有登录后才能访问的接口 ---》要携带access-token ---》3分钟之后过期了,就用不了了 access过期,再发送请求携带refresh--token ---》通过refresh再签发一个新的access--发送请求使用access
【4】认证
认证类:局部,全局使用,局部禁用 认证类:JWTTokenUserAuthentication 权限类:IsAuthenticated 认证类原理: 1 去请求头中,取出 HTTP_Authorization,使用 空格 分割,取出 1的位置--》access 2 如果取不到,没有抛异常,直接返回了None---》相当于认证类没有 header = self.get_header(request) if header is None: return None 3 必须使用权限类原因:校验用户是否通过登录认证,没有带access,就通不过,在这就拦截了
【二】simple-jwt配置文件
# JWT配置 SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期 # 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整 # 是否自动刷新Refresh Token 'ROTATE_REFRESH_TOKENS': False, # 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中 'BLACKLIST_AFTER_ROTATION': False, 'ALGORITHM': 'HS256', # 加密算法 'SIGNING_KEY': settings.SECRET_KEY, # 签名密匙,这里使用Django的SECRET_KEY # 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间 "UPDATE_LAST_LOGIN": False, # 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。 "VERIFYING_KEY": "", "AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。 "ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。 "JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。 "JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。 "LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。 # 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer" "AUTH_HEADER_TYPES": ("Bearer",), # 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION" "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", # 用户模型中用作用户ID的字段。默认为"id"。 "USER_ID_FIELD": "id", # JWT负载中包含用户ID的声明。默认为"user_id"。 "USER_ID_CLAIM": "user_id", # 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。 "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", # 用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。 "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), # JWT负载中包含令牌类型的声明。默认为"token_type"。 "TOKEN_TYPE_CLAIM": "token_type", # 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。 "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", # JWT负载中包含JWT ID的声明。默认为"jti"。 "JTI_CLAIM": "jti", # 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。 "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", # 滑动令牌的生命周期。默认为5分钟。 "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5), # 滑动令牌可以用于刷新的时间段。默认为1天。 "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", # 用于列出或撤销已失效JWT的序列化器。 "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", } ###在咱们项目配置文件中配置 from datetime import timedelta SIMPLE_JWT={ 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), # Access Token的有效期 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期 }
【三】定制jwt登录返回格式和荷载内容
【1】定制返回格式
# 1 要求的格式-->但token认证 {code:100,msg:登录成,token:asdfa.asdfa.asdfasd} # 2 使用步骤: 1 写个序列化类类名自己定义,class MyTokenObtainPairSerializer(TokenObtainPairSerializer): 2 重写validate方法,方法返回什么,登录成功的格式就是什么 3 配置文件配置: SIMPLE_JWT={ "TOKEN_OBTAIN_SERIALIZER": "app01.serialzier.MyTokenObtainPairSerializer", }
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.views import TokenObtainPairView # 这里最终继承的就是serializers.Serializer class MyTokenObtainPairSerializer(TokenObtainPairSerializer): def validate(self, attrs): ''' 自定义返回格式 self.user 就是当前登录用户 super().validate(attrs) 返回的数据就是refresh和access 这里写完自定义返回格式还需要到配置文件中配置一下 ''' old_data = super().validate(attrs) data = {"code": 200, "msg": "登录成功", "username": self.user.username, # "refresh": old_data.get("refresh"), # "access": old_data.get("access")} "token": old_data.get("access")} return data
【2】更改荷载
2 使用步骤: 1 写个序列化类,重写validate方法,重新一个类方法 @classmethod def get_token(cls, user): token = super().get_token(user) token['name'] = user.username token['email'] = user.email return token 2 配置文件配置: SIMPLE_JWT={ "TOKEN_OBTAIN_SERIALIZER": "app01.serialzier.MyTokenObtainPairSerializer", }
-
获取到的荷载通过base64解码
import base64 res = base64.b64decode("eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyNjg5MjM3LCJpYXQiOjE3MjI2ODU2OTcsImp0aSI6IjRkYzljZjlkMjEyZDQzMTNiOTFiZmFiNTM5ZjEzYjZkIiwidXNlcl9pZCI6MSwibmFtZSI6ImtuaWdodCIsImVtYWlsIjoiMTQ0QHFxLmNvbSJ9=") print(res) # b'{"token_type":"access","exp":1722689237,"iat":1722685697,"jti":"4dc9cf9d212d4313b91bfab539f13b6d","user_id":1,"name":"knight","email":"144@qq.com"}'
【四】多方式登录
【1】扩写auth的user表,增加手机号字段
配置文件:扩写auth的user表,必须配置它 AUTH_USER_MODEL='app01.UserInfo' 注意:如果要扩写auth的user表--》必须在没迁移之前就扩写,一旦迁移之后,再扩写就会出问题--》尽量不要这样做 需要: 1 删库---》(删除之前要保存库的数据) 2 删除迁移文件 app/migrations下的 还有Django内置的lib/site-packages/django/admin和auth的缓存文件
【2】多方式登录
1 登录方式 用户名+密码 手机号+密码 邮箱+密码 2 前端如何传 {username:'asdf/1893434343/3@qq.com',password:'123456'}
-
视图类views.py
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_simplejwt.authentication import JWTTokenUserAuthentication from rest_framework.permissions import IsAuthenticated from .serializer import LoginSerializer class LoginView(APIView): def post(self, request): """ 校验方法一: 1 取出用户名 密码 2 去数据库中校验 3 校验通过 签发token 4 校验不通过返回错误信息 校验方法二: 1 实例化得到序列化类对象 2 序列化类对象调用 --- is_valid --- 走字段自己的校验,局部钩子,全局钩子校验 在全局钩子中:取出用户名,密码 ---去数据库校验 ---校验通过 ,签发token 3 is_valid 校验通过之后,继续往下走,取出token(refresh和access) 4 不通过返回错误信息 """ # 校验方法二: serializer = LoginSerializer(data=request.data) serializer.is_valid(raise_exception=True) refresh = serializer.context.get('refresh') access = serializer.context.get('access') return Response({"code": 200, "msg": "登录成功", 'refresh': refresh, 'access': access}) # 校验方法一:一条筋全在视图类中做校验 # username = request.data.get('username') # password = request.data.get('password') # # 2 正则匹配是 手机号+密码 用户+密码 邮箱+密码 # import re # if re.match(r'^1[3-9][0-9]{9}$', username): # # 手机登录 # user = UserInfo.objects.filter(mobile=username).first() # elif re.match(r'^.+@.+$', username): # # 邮箱登录 # user = UserInfo.objects.filter(email=username).first() # else: # # 账号登录 # user = UserInfo.objects.filter(username=username).first() # # # 3 校验密码 # if user and user.check_password(password): # # 签发token--》借助于simple-jwt # # simple-jwt 提供的RefreshToken 可以根据用户对象,签发 refresh和access 的token值 # refresh = RefreshToken.for_user(user) # refresh_token = str(refresh) # refresh # access_token = str(refresh.access_token) # access # return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh_token, 'access': access_token}) # else: # return Response({'code': 999, 'msg': '用户名或密码错误'})
-
序列化类
from rest_framework import serializers from .models import UserInfo class LoginSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField() # 重写validate def validate(self, attrs): username = attrs.get('username') password = attrs.get('password') # 正则匹配 手机号+密码 用户+密码 邮箱+密码 import re if re.match(r'^1[3-9][0-9]{9}$', username): # 手机号登录 user = UserInfo.objects.filter(mobile=username).first() elif re.match(r'^.+@.+$', username): user = UserInfo.objects.filter(email=username).first() else: user = UserInfo.objects.filter(username=username).first() # 校验密码 if user and user.check_password(password): # 签发token # simple-jwt 提供的RefreshToken 可以根据用户对象,签发refresh 和access 的token值 refresh = RefreshToken.for_user(user) refresh_token = str(refresh) # refresh access_token = str(refresh.access_token) # access self.context['refresh'] = refresh_token self.context['access'] = access_token return attrs else: raise APIException('用户名或密码错误')
-
在总路由urls.py中添加一条路由
from app01.views import LoginView urlpatterns = [ path('mul_login/',LoginView.as_view()) ]
-
此时前端可以使用三种登录方式访问了
【3】总结
# 在以后,如果想用 AUTH的user表作为用户表,无论是否扩展该表 -1 认证:固定的 class BookView(APIView): authentication_classes = [JWTTokenUserAuthentication] permission_classes = [IsAuthenticated] -2 签发: -1方法 使用simple-jwt提供的(只能改返回的东西,请求的改不了) -修改返回格式 -修改荷载 -2方法 这时如果有需求可以 自定义 login,实现多方式登录--》基于auth的user表签发 # 后期,可能会自定义用户表,签发和认证 -1 认证:自定义认证类 -2 签发:自己写 # 以后再公司中,一个项目,就一个用户表? 有可能有多个用户表 例如外卖平台有 商家用户 骑手用户 普通用户
【五】自定义用户表签发
-
路由urls.py
path('my_login/', MyLoginView.as_view()),
-
视图类views.py
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_simplejwt.authentication import JWTTokenUserAuthentication from rest_framework.permissions import IsAuthenticated from .serializer import LoginSerializer, MyLoginSerializer from rest_framework.generics import GenericAPIView class CommonAPIView(): def post(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) refresh = serializer.context.get('refresh') access = serializer.context.get('access') return Response({"code": 200, "msg": "登录成功", 'refresh': refresh, 'access': access}) class MyLoginView(GenericAPIView, CommonAPIView): serializer_class = MyLoginSerializer
-
序列化类 serializer.py
from .models import User class MyLoginSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField() def validate(self, attrs): username = attrs.get('username') password = attrs.get('password') user = User.objects.filter(username=username, password=password).first() assert user, APIException('用户名或密码错误!') # 签发token refresh = RefreshToken.for_user(user) self.context['refresh'] = str(refresh) self.context['access'] = str(refresh.access_token) return attrs
【六】认证
-
auth.py
from rest_framework.exceptions import APIException from rest_framework_simplejwt.authentication import JWTAuthentication from .models import User # 方式一:继承JWTAuthentication的认证 class JWTCommonAuthentication(JWTAuthentication): # 认证 def authenticate(self, request): token = request.META.get('HTTP_AUTHORIZATION') if token: # 验证token (使用 JWTAuthentication 中的get_validated_token方法) res = self.get_validated_token(token) # 得到是荷载 # 从荷载中取出 用户的 id user_id = res.get('user_id') # 查询当前用户 user = User.objects.get(pk=user_id) return user, token else: raise APIException('没有携带认证信息') from rest_framework.authentication import BaseAuthentication from rest_framework_simplejwt.tokens import AccessToken # 方式二:继承BaseAuthentication进行认证 class JWTCommonAuthentication(BaseAuthentication): # 认证 def authenticate(self, request): token = request.META.get('HTTP_AUTHORIZATION') if token: # 验证token try: res = AccessToken(token) # 得到是荷载 except Exception: raise APIException('token认证失败') # 从荷载中取出 用户的 id user_id = res.get('user_id') # 查询当前用户 user = User.objects.get(pk=user_id) return user, token else: raise APIException('没有携带认证信息')
使用
# 视图类 views 配置 # 不需要再搭配permission了 # 放在请求头中:Authorization:adsfasfd.asdfasdf.asfdasd from .auth import JWTCommonAuthentication class BookView(APIView): authentication_classes = [JWTCommonAuthentication] def get(self, request): return Response('是一本好书')
【七】权限介绍
# 工作中的权限有 三大类 1 访问控制列表:ACL--》针对于互联网用户 例如抖音有三个功能权限:评论,点赞,开直播 张三用户:[评论,点赞] 李四用户:[评论,点赞,开直播] 不同用户拥有的权限不一样 2 RBAC 基于角色的访问控制--》公司内部 -开发部:张三 李四 [删除代码,查看代码] -运维部:王五 [操作服务器] -总裁办:gdy [发工资] 3 ABAC:基于属性的访问控制--》在RBAC的基础上扩展
【1】django自带的admin后台管理访问
-
http://127.0.0.1:8000/admin/ 进入后台管理进行操作
# apps.py 可以修改后台中的应用名 from django.apps import AppConfig class App01Config(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'app01' verbose_name = '应用一'
# admin.py 将模型(models)注册到Django的后台管理界面中 from django.contrib import admin # Register your models here. from .models import Book, User, UserInfo # 将模型(models)注册到Django的后台管理界面中,这样就可以通过Django的admin站点来管理这些模型的数据了 admin.site.register(Book) admin.site.register(UserInfo) admin.site.register(User)
# models.py 模型表,可以在里面修改表名 from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): mobile = models.CharField(max_length=11, default=True) class User(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) age = models.IntegerField() class Book(models.Model): name = models.CharField(max_length=32) price = models.IntegerField() # 修改后台管理处的表名 class Meta: verbose_name = '图书表' verbose_name_plural = '图书表'
【2】auth的user表,密码加密 -- 同样的明文,加密后不一样
pbkdf2_sha256$600000$rgyxVTKxAk3ZJWuGXLKVui$WkXhljqsQ92svImlpqyIt6SwTeINBJkZfGI9e7BLreI= pbkdf2_sha256:加密方式 600000:过期时间 H9kCyedgKxwUeD5EDr6aqJ:盐 j1jLbChb7irs4O8v71LUKgmcRHX5CAl/ka/UWkBw9o0= :密文 auth的user表的对象有俩方法 可以在需要自定义的时候使用: # 用于验证用户输入的原始密码(raw_password)是否与数据库中存储的加密密码(enc_password)相匹配 from django.contrib.auth.hashers import check_password user.check_password(明文密码) # 创建一个新用户并设置其密码 from django.contrib.auth.hashers import make_password make_password(明文密码)--》User.object.create_user()