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后台管理访问

# 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()
        
  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值