simple-jwt使用
# 使用simple-jwt 快速体验 pip install djangorestframework-simplejwt # python manage.py createsuperuser '''创建超级用户是为了方便管理 Django 的后台管理界面,而 simple-jwt 的使用则是为了在 Django REST Framework 中实现 JWT 认证和用户身份验证。这两个步骤是相互独立的,但通常都需要在你的项目中进行。''' # 用户名: admin # 电子邮件地址: 333@qq.com # 密码: 123123 - 登陆签发:默认使用auth的user表--》创建个用户--》能登录了 路由: 登陆接口了 path('login/', token_obtain_pair), # 127.0.0.1:8080/app01/login-->post--》用户名密码就能登陆 - 认证 class UserTokenView(GenericViewSet, mixins.CreateModelMixin): # 必须登陆后才能新增 authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated]
创建一个超级管理员
- from rest_framework_simplejwt.views import token_obtain_pair
# 登陆接口 from rest_framework_simplejwt.views import token_obtain_pair from three.views import BookViewSet, UserTokenView, TeyView # 自动生成路由 from rest_framework.routers import SimpleRouter, DefaultRouter from django.urls import path, include # 2 实例化得到对象 # router = SimpleRouter() router = DefaultRouter() # 3 执行对象的方法 router.register('User', UserTokenView, 'User') urlpatterns = [ path('login/', token_obtain_pair), path('try/', TeyView.as_view()), ] # 5 把自动生成的路由,放到 urlpatterns中 urlpatterns += router.urls
# 序列化类 from rest_framework import serializers from .models import Book,User,UserToken # class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' #---------------------------------- from rest_framework import mixins # 认证 from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework.permissions import IsAuthenticated class UserTokenView(GenericViewSet, mixins.CreateModelMixin): # 获取表中信息 queryset = Book # 序列化类 serializer_class = BookSerializer # 必须登陆之后才能新增 authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated]
使用步骤
-
先登陆接口复制
-
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzEzNDUwMjY1LCJpYXQiOjE3MTM0NDk5NjUsImp0aSI6ImIyNWFjNmI3NDQ3ZjQ3NzFhYjViYjM4ZDYzMjhiNTkzIiwidXNlcl9pZCI6MX0.xcqKK-YY2fpMkYKre6VpUOfmWdr2V_i-XL4R5pkOlCE"
-
对http://127.0.0.1:8300/three/User/ 这个接口header key:Authorization , vlaue:access的值
【 1 】simple-jwt配置文件
# 用户模型 # 应用程序.表名 AUTH_USER_MODEL = "one.UserInfo" # settings.py配置文件 import datetime SIMPLE_JWT = { # token有效时长 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30), # token刷新后的有效时间 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1), }
- accessToken:用户获取数据权限
- refreshToken:用来获取新的accessToken
# 用户模型 # 应用程序.表名 AUTH_USER_MODEL = "one.UserInfo" # 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), # 用于生成access和刷refresh的序列化器。 "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", }
【 2 】jwt定制登陆的返回格式
这些的前提条件就是要先写一个登陆的接口
- path('login/', token_obtain_pair)``token_obtain_pair是Django REST Framework SimpleJWT提供的系统自带的用于获取访问令牌(access token)和刷新令牌(refresh token)的登录认证接口。
# 登陆接口 from rest_framework_simplejwt.views import token_obtain_pair from three.views import UserTokenView # 自动生成路由 from rest_framework.routers import SimpleRouter, DefaultRouter from django.urls import path, include # 2 实例化得到对象 # router = SimpleRouter() router = DefaultRouter() # 3 执行对象的方法 router.register('User', UserTokenView, 'User') urlpatterns = [ path('login/', token_obtain_pair), ] # 5 把自动生成的路由,放到 urlpatterns中 urlpatterns += router.urls
# views.py from rest_framework import mixins # 认证 from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework.permissions import IsAuthenticated class UserTokenView(GenericViewSet, mixins.CreateModelMixin): # 获取表中信息 print(11111) queryset = Book # 序列化类 serializer_class = CommonTokenserializer print(serializer_class) #
# 必须登陆之后才能新增 authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated] from rest_framework_simplejwt.serializers import TokenObtainPairSerializer # 写一个序列化类。继承TokenObtainPairSerializer —— >> 重写validate方法。 class CommonTokenserializer(TokenObtainPairSerializer): def validate(self, attrs): """ 自定义返回的格式 """ # 获取父类的原始数据 old_data = super().validate(attrs) data = {'code': 100, 'msg': '登录成功成功', 'username': self.user.username, # 刷新令牌 'refresh': old_data.get('refresh'), # 访问令牌 'access': old_data.get('access') } return data
-
配置文件
SIMPLE_JWT = { # token有效时长 访问 # 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30), # token刷新后的有效时间 刷新 # 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1), "TOKEN_OBTAIN_SERIALIZER": "three.serial.CommonTokenserializer" }
【 3 】 定制payload格式
-
这个跟登陆返回格式大致一样
-
序列化类
- CommonTokenObtainSerializer 类继承自 TokenObtainPairSerializer,这意味着它继承了 TokenObtainPairSerializer 类的所有属性和方法,并且可以在此基础上进行定制化。
- get_token(cls, user) 方法是一个类方法,用于获取令牌。它首先调用父类的 get_token 方法来获取令牌对象,然后将用户的用户名添加到令牌中,并返回更新后的令牌对象。
- validate(self, attrs) 方法是一个局部钩子,用于验证输入数据。在这个方法中,首先调用父类的 validate 方法来验证输入的属性,并获取到验证后的结果。然后,从验证结果中获取令牌的 refresh 和 access 属性,并将其与用户的用户名一起包装成一个字典返回给客户端。这个方法的主要作用是在用户登录成功后返回额外的信息,比如用户名和自定义的消息。
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer class CommonTokenserializer(TokenObtainPairSerializer): @classmethod def get_token(cls, user): # super() --> 代指父类的对象 # 对象调用类的绑定方法,会自动把对象的类传入。 token = super().get_token(user) token['name'] = user.username return token # 局部钩子 def validate(self, attrs): dict_res = super().validate(attrs) data = { 'code': 1000, 'message': '登陆成功', # 获取用户信息 'username': self.user.username, 'refresh': dict_res.get('refresh'), 'access': dict_res.get('access') } return data
SIMPLE_JWT = { "TOKEN_OBTAIN_SERIALIZER": "three.serial.CommonTokenserializer" }
【 4 】多方式登陆
-
扩写auth的user表—》加入mobile字段
-注意:
-之前迁移过–》auth的user表已经生成了,就不能扩展写
-方案一: 创建新项目,从头做
-方案二:删库,删除迁移记录(咱们自己app和auth和admin)
-以后如果要扩写auth的user表,必须在迁移之前就定好,写好
-
编写登陆接口
这里建议直接删除省事还可以直接就是要我们原本定义的User表中的数据。
# mdoels.py from django.db import models from django.contrib.auth.models import AbstractUser class User(models.Model): username = models.CharField(max_length=64) password = models.CharField(max_length=64) phone = models.CharField(max_length=11) email = models.EmailField(unique=True) # 添加 email 字段 user_type = models.IntegerField(choices=((1, '注册用户'), (2, '普通管理员'), (3, '超级管理员')), default=1)
要么就是使用在数据迁移之后出现的UserInfo表
UserInfo表它继承自Django提供的 AbstractUser 模型。AbstractUser 是 Django 内置的用户模型,它包含了常见的用户属性,如用户名、密码和电子邮件地址等 。 我们只需要在这个基础上在models.py文件当添加一个phone字段就可以了。
from django.db import models from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): phone = models.CharField(max_length=11) # 这里可以添加额外的字段,不需要再次定义 username、password 和 email 字段
-
urls.py
-
http://127.0.0.1:8110/one/user/login/
from django.urls import path, include # 登陆接口 from rest_framework_simplejwt.views import token_obtain_pair # 自动生成路由 from rest_framework.routers import SimpleRouter, DefaultRouter from one.views import UserJWTokenView,UserJWTOneView,PublishViews # 2 实例化得到对象 # router = SimpleRouter() router = DefaultRouter() # 多方式登陆 router.register('user', UserJWTokenView, 'user') # 自定义用户表,手动签发和认证 router.register('user1', UserJWTOneView, 'user1') # 先登陆在访问 router.register('publish',PublishViews , 'publish') urlpatterns = [ # 系统自带的登陆接口 path('login/', token_obtain_pair), # path('api/v1',include(router.urls))/ ] # 5 把自动生成的路由,放到 urlpatterns中 urlpatterns += router.urls
-
views.py
from rest_framework.viewsets import GenericViewSet # 限制响应方法 from rest_framework.decorators import action from .serializer import LoginJWTSerial, LoginJWTOneSerial # 响应 from rest_framework.response import Response # 认证类 from .authent import JWTOurSerial class UserJWTokenView(GenericViewSet): serializer_class = LoginJWTSerial @action(methods=['POST'], detail=False) def login(self, request, *args, **kwargs): # 正常逻辑:取出手机号 / 用户名 / 邮箱 + 密码 - -》去数据校验 - -》校验通过 -->签发token - -》返回给前端 # 现在直接在序列化定义 serializer = self.get_serializer(data=request.data) if serializer.is_valid(): # 执行 三层认证 # 校验通过:会把user,access和refresh都放到序列化类对象中--》返回给前端、 # 现在在视图类中----》有个序列化类--》把视图类中变量给序列化类---》序列化类的变量给视图类--》借助于context给[字典] refresh = serializer.context.get('refresh') access = serializer.context.get('access') return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access}) else: return Response({'code': 101, 'msg': serializer.errors})
-
序列化类
# 序列化模块 from rest_framework import serializers import re from rest_framework_simplejwt.tokens import RefreshToken from one.models import User # 异常捕获 from rest_framework.exceptions import ValidationError # 多方式登陆 class LoginJWTSerial(serializers.Serializer): # 判断是用户名、手机号、邮箱等等 # 对用户进行序列化 username = serializers.CharField() password = serializers.CharField() def _get_user(self,attrs): # 【1】校验用户 username = attrs.get('username') password = attrs.get('password') # 我们要去数据库 查询用户 ---> username 可能是 用户名、手机号、邮箱 # 所以我们要在登陆的时候进行检验(使用正则) if re.match(r'^1[3-9][0-9]{9}$', username): # 手机号 user = User.objects.filter(phone=username).filter() elif re.match(r'^.+@.+$',username): user = User.objects.filter(email=username).filter() else: user = User.objects.filter(username=username).filter() # 校验user是否在User表中是否存在 if user and user.first().password == password: print(user) return user.first() else: raise ValidationError('用户名或者密码错误!!!') def validate(self, attrs): # 取出 手机号/用户名/邮箱+密码--》数据库校验--》校验通过签发 access和refresh,放到context中 user = self._get_user(attrs) # 验证token token = RefreshToken.for_user(user) self.context['access'] = str(token.access_token) self.context['refresh'] = str(token) return attrs # 不返回不行:因为源码中校验了是否为空--》
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzEzNzkyMzc2LCJpYXQiOjE3MTM3OTIwNzYsImp0aSI6IjllZmQxM2E5MDk2ZTQ0OGFhZjU5ODQ1OGNiOWFkMTRlIiwidXNlcl9pZCI6Mn0.uR1mUGDW703oQkbJhDedm3k8o1AiDGOsWCB4UVEXhvQ
是一个 JSON Web Token(JWT)。JWT 通常由三部分组成,它们用点号 . 分隔开来:
- Header(头部):包含了该 JWT 使用的算法等信息。
- Payload(荷载):包含了实际的数据,比如用户的身份信息以及其他数据。
- Signature(签名):用于验证 JWT 的真实性,确保它没有被篡改过。
在你提供的 JWT 中,第一个逗号之前的部分就是头部,第二个逗号之前的部分是荷载,第三个逗号之后的部分是签名。
总结:
# 1 校验数据,放到序列化类的 validate中,而不放在视图类的方法中乐 # 2 视图类和序列化类直接交互变量 serializer.context # 3 user.check_password 必须是auth的user表,校验密码使用它 # 4 attrs必须返回值,返回空报错 # 5 视图类的方法校验失败的else中:也要return Response # 6 如何签发token token = RefreshToken.for_user(user) self.context['access'] = str(token.access_token) self.context['refresh'] = str(token)
【 5 】自定义用户表,手动签发和认证
-
urls.py
from django.urls import path, include # 登陆接口 from rest_framework_simplejwt.views import token_obtain_pair # 自动生成路由 from rest_framework.routers import SimpleRouter, DefaultRouter from one.views import UserJWTOneView,PublishViews # 2 实例化得到对象 # router = SimpleRouter() router = DefaultRouter() # 自定义用户表,手动签发和认证 router.register('user1', UserJWTOneView, 'user1') # 先登陆在访问 router.register('publish',PublishViews , 'publish') urlpatterns = [ # 系统自带的登陆接口 path('login/', token_obtain_pair), # path('api/v1',include(router.urls))/ ] # 5 把自动生成的路由,放到 urlpatterns中 urlpatterns += router.urls
-
views.py
class UserJWTOneView(GenericViewSet): serializer_class = LoginJWTOneSerial @action(methods=['POST'], detail=False) def login(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): # 执行 三层认证 # 校验通过:会把user,access和refresh都放到序列化类对象中--》返回给前端、 # 现在在视图类中----》有个序列化类--》把视图类中变量给序列化类---》序列化类的变量给视图类--》借助于context给[字典] refresh = serializer.context.get('refresh') access = serializer.context.get('access') return Response({'code': 200, 'msg': '登录成功', 'refresh': refresh, 'access': access}) else: return Response({'code': 101, 'msg': serializer.errors}) # 登陆之后的测试 ---------------------------------------------------------- class PublishViews(GenericViewSet): authentication_classes = [JWTOurSerial] def list(self, request): return Response('查看成功!!!')
-
序列化类
class LoginJWTOneSerial(serializers.Serializer): # 用户名 username = serializers.CharField() password = serializers.CharField() def _get_user(self, attrs): # 1 校验用户 username = attrs.get('username') password = attrs.get('password') user = User.objects.filter(username=username, password=password).first() # 校验用户是否 if user: return user else: raise ValidationError('用户名或密码错误') def validate(self, attrs): user = self._get_user(attrs) token = RefreshToken.for_user(user) self.context['access'] = str(token.access_token) self.context['refresh'] = str(token) return attrs
-
自定义认证类
from rest_framework_simplejwt.authentication import JWTAuthentication from .models import User class JWTOurAuth(JWTAuthentication): def authenticate(self, request): # 取出用户携带的access---》放请求头中:Authorization token = request.META.get('HTTP_AUTHORIZATION') if token: # 校验token--》validated_token 返回的就是可以信任的payload validated_token = self.get_validated_token(token) user_id = validated_token['user_id'] user = User.objects.filter(pk=user_id).first() return user, token else: raise AuthenticationFailed('请携带登录信息')
- http://127.0.0.1:8110/one/user1/login/ POST
- http://127.0.0.1:8110/one/publish/ GET
- 序列化类 LoginJWTOneSerial:
- 定义了两个字段 username 和 password,用于接收用户提交的用户名和密码。
- _get_user 方法用于根据提交的用户名和密码查询用户,如果找到用户,则返回用户对象,否则抛出 ValidationError 异常。
- validate 方法用于对提交的数据进行验证,调用 _get_user 方法验证用户,并使用 RefreshToken.for_user 方法为用户生成 JWT,将生成的 access token 和 refresh token 放入 context 中返回。
- 用户模型 User:
- 定义了用户的基本信息,包括用户名、密码、手机号、邮箱和用户类型等字段。
- 使用方式:
- 用户可以通过 http://127.0.0.1:8110/one/user1/login/ 地址发送 POST 请求来登录,提交用户名和密码。
- 登录成功后会返回包含 access token 和 refresh token 的响应。
- 用户可以通过 http://127.0.0.1:8110/one/publish/ 地址发送 GET 请求来获取发布内容列表,但需要在请求头中带上有效的 JWT 进行身份验证。
双 token 验证流程
双 token 验证机制,其中 accessToken 过期时间较短,refreshToken 过期时间较长。当 accessToken 过期后,使用 refreshToken 去请求新的 token。
- 用户登录向服务端发送账号密码,登录失败返回客户端重新登录。登录成功服务端生成 accessToken 和 refreshToken,返回生成的 token 给客户端。
- 在请求拦截器中,请求头中携带 accessToken 请求数据,服务端验证 accessToken 是否过期。token 有效继续请求数据,token 失效返回失效信息到客户端。
- 客户端收到服务端发送的请求信息,在二次封装的 axios 的响应拦截器中判断是否有 accessToken 失效的信息,没有返回响应的数据。有失效的信息,就携带 refreshToken 请求新的 accessToken。
- 服务端验证 refreshToken 是否有效。有效,重新生成 token, 返回新的 token 和提示信息到客户端,无效,返回无效信息给客户端。
- 客户端响应拦截器判断响应信息是否有 refreshToken 有效无效。无效,退出当前登录。有效,重新存储新的 token,继续请求上一次请求的数据。
注意事项
- 短token失效,服务端拒绝请求,返回token失效信息,前端请求到新的短token如何再次请求数据,达到无感刷新的效果。
- 服务端白名单,成功登录前是还没有请求到token的,那么如果服务端拦截请求,就无法登录。定制白名单,让登录无需进行token验证。
关于双token认证的问题
大佬搭建的接口平台
YApi-高效、易用、功能强大的可视化接口管理平台
# 1 使用auth的user表---》只能传用户名 ,密码校验 # 2 项目中:手机号/用户名/邮箱 + 密码--》也可以登录成功--》simple-jwt就不行了 # 3 自己定制登陆接口--》使用auth的user表 -签发自己签发 -认证继续用 simple-jwt的认证即可 # 4 编写一个多方式登陆接口 - 扩写auth的user表---》加入mobile字段 -坑: -之前迁移过--》auth的user表已经生成了,就不能扩展写 -方案一: 创建新项目,从头做 -方案二:删库,删除迁移记录(咱们自己app和auth和admin) -以后如果要扩写auth的user表,必须在迁移之前就定好,写好 - 编写登陆接口
# 用户模型 # 应用程序.表名 AUTH_USER_MODEL = "one.UserInfo"
Tracking file by folder pattern: migrations
Username: admin
Email address: 363@qq.com
Warning: Password input may be echoed.
Password: xxxxxx
Warning: Password input may be echoed.
Password (again): xxxxxx
- 序列化类 LoginJWTOneSerial:
-
-
-
-
-
-
-
- path('login/', token_obtain_pair)``token_obtain_pair是Django REST Framework SimpleJWT提供的系统自带的用于获取访问令牌(access token)和刷新令牌(refresh token)的登录认证接口。
-
还没有评论,来说两句吧...