jwt认证
·
【一】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()
更多推荐


所有评论(0)