首页 > 其他分享 >DRF之JWT认证

DRF之JWT认证

时间:2024-04-27 20:23:39浏览次数:24  
标签:username self JWT 认证 token user data DRF

DRF之JWT认证

【一】JWT

  • WT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络上传输声明的一种紧凑且自包含的方式。JWT 可以使用 HMAC 算法或是使用 RSA 或 ECDSA 等公钥/私钥对进行签名。通常,它用于在身份提供者和服务之间传递被认证的用户身份信息,以便于在用户和服务之间安全地传递声明。
  • JWT 主要由三部分组成,用.分隔开:
    1. Header(头部):包含了类型(typ)和所使用的算法(alg)等信息,通常是 {"alg": "HS256", "typ": "JWT"}
    2. Payload(负载):包含了要传递的声明信息,如用户的身份、角色等,以及其他自定义的数据,例如 {"sub": "user123", "role": "admin"}
    3. Signature(签名):将编码后的 Header 和 Payload 以及一个秘密(对称加密)或公钥/私钥(非对称加密)一起进行签名,以保证数据的完整性和真实性。
  • JWT 的优势在于它的紧凑性和自包含性,使得它适合在不同的环境中进行传递,如在 HTTP 请求头、URL 参数或 POST 参数中。JWT 的缺点是一旦签发后,其内容无法更改,也无法撤销,除非设置了较短的过期时间并在服务端进行验证。
  • 在 Web 开发中,JWT 被广泛用于实现用户认证和授权机制,常见的应用场景包括单点登录(SSO)、API 认证等。

【二】SimpleJwt

【0】SimpleJwt源码

  • SimpleJwt其实与drf类似,也是一个app,需要在django项目文件中注册

【0.1】登录

# 登录的入口
from rest_framework_simplejwt.views import token_obtain_pair

image-20240426204820103

【0.2】认证

# 认证类的入口
from rest_framework_simplejwt.authentication import JWTAuthentication

image-20240427195114363

【1】SimpleJwt配置文件

# 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】基本使用

【2.1】使用SimpleJwt登录

  • urls.py
    • token_obtain_pair,路由中配置即可
from django.urls import path, include
# 导入token_obtain_pair即可
from rest_framework_simplejwt.views import token_obtain_pair

urlpatterns = [
    path('login/', token_obtain_pair),
]

【2.2】使用SimpleJwt认证

  • 【注】进行认证时,携带的请求头格式由配置文件中配置

image-20240427165424249

image-20240427165430378

############# views.py ###########
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import GenericViewSet
from .models import Book
from .ser import CommonSerializer


class CommonView(GenericViewSet):
    queryset = Book.objects.all()
    serializer_class = CommonSerializer
    # 将 JWTAuthentication 配置到 authentication_classes 即可
    authentication_classes = [JWTAuthentication]
    # 【注】使用JWT默认的认证类,需要配合 IsAuthenticated 的权限类
    permission_classes = [IsAuthenticated]

【3】基于SimpleJwt自定制

【3.1】定制登录成功返回格式

  • 继承TokenObtainPairSerializer并重写validate方法并修改返回值来控制返回格式
【3.1.1】重写validate方法
【3.1.1.1】直接使用simple-jwt的登录接口
############### urls.py #########
from rest_framework_simplejwt.views import token_obtain_pair


urlpatterns = [
    path('login/', token_obtain_pair),
]
########### ser.py ##########
class LoginSerializer(TokenObtainPairSerializer):
    '''
    登录接口调用simple-jwt的序列化类,重写validate定制返回格式
    '''

    def validate(self, attrs):
        data = super().validate(attrs)
        response = {
            'status': 100,
            'msg': '登录成功',
            'token': data.get('access')
        }
        return response
【3.1.1.2】使用context获取token
  • 自定义登录接口
  • 在序列化类中进行校验
  • 由于序列化类继承TokenObtainPairSerializer,可以直接调用simple-jwtget_token方法获得token 对象
  • 将token 放在序列化类对象的context属性
  • view中通过ser.context获取序列化类中放置的数据
class UserView(GenericViewSet):
    serializer_class = LoginSerializer
    authentication_classes = []

    @action(methods=['POST'], detail=False)
    def login(self, request):
        ser = self.get_serializer(data=request.data)
        if ser.is_valid():
            token = ser.context['token']
            refresh = ser.context['refresh']
            return Response({'code': 100, 'msg': '登录成功', 'token': token, 'refresh': refresh})
        else:
            return Response({'code': 101, 'msg': ser.errors})
################ ser.py ############
class LoginSerializer(TokenObtainPairSerializer):
    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()
        if not user:
            raise ValidationError("用户名或密码错误")
            
        token = self.get_token(user)
        self.context['refresh'] = str(token)
        self.context['token'] = str(token.access_token)
        return attrs
【3.1.2】重写data方法
############ views.py ##########
class UserView(GenericViewSet):
    # 局部禁用认证和权限
    authentication_classes = []
    permission_classes = []
    serializer_class = RegisterSerializer

    @action(methods=['POST'], detail=False)
    def register(self, request):
        ser = self.get_serializer(data=request.data)
        ser.is_valid(raise_exception=True)
        ser.save()
        return Response(ser.data)
############### ser.py ###########
class RegisterSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        fields ='__all__'

    def create(self, validated_data):
        user = UserInfo.objects.create_user(**validated_data)
        return user

    @property
    def data(self):
        # 重写data方法定制返回格式
        res = super().data
        return {'code': 100, 'msg': '注册成功', 'result': res}

【3.2】定制认证类

  • 继承JWTAuthentication并重写authenticate方法
  • 由于继承了JWTAuthentication,所以可以直接调用原来进行验证的方法
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.exceptions import AuthenticationFailed


class CommonAuthentication(JWTAuthentication):
    def authenticate(self, request):
        token = self.get_header(request)
        if not token:
            raise AuthenticationFailed("请先登录或携带token信息")
        payload = self.get_raw_token(token)
        validated_token = self.get_validated_token(payload)
        user = self.get_user(validated_token)
        if not user.locked:
            return user, validated_token
        else:
            raise AuthenticationFailed("当前用户已冻结")

【4】多方式登陆

  • 继承TokenObtainPairSerializer并重写validate方法就能够实现手动校验
class LoginSerializer(TokenObtainPairSerializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def _get_user(self, username, password):
        if re.match('^(13|14|15|18)[0-9]{9}$', username):
            user = User.objects.filter(mobile=username, password=password).first()
        elif re.match('^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+\.[a-z]+$', username):
            user = User.objects.filter(email=username, password=password).first()
        else:
            user = User.objects.filter(username=username, password=password).first()
        return user

    def get_token(cls, user):
        token = super().get_token(user)
        token['name'] = user.username
        return token

    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        user = self._get_user(username, password)
        if not user:
            raise ValidationError("用户名或密码错误")
        token = self.get_token(user)
        self.context['refresh'] = str(token)
        self.context['token'] = str(token.access_token)
        return attrs

【三】自定义JWT认证

  • 基于base64、md5和django密钥进行加密
    • headers:使用base64对包含头信息的字典进行加密
    • payload:载荷中包含用户的用户名和密码及其他信息
    • Signature:签名由经过base64加密过的headers和payload并加入django密钥作为盐,由md5进行加密
  • 由于md5和base64,严格来说都不是加密算法,同样的数据经过同样的加密方式获取的数据一致
  • 通过后端加密的数据与前端传入的token进行比对
  • 由于我们的django密钥是绝对私密的,经过比对,就能校验出该token是否由我们签发的
################# 定义类 ##################
import hashlib
import json
import base64
import time
from django.conf import settings


class CommonJWT:
    def __init__(self, header=None, salt=None, expired=180):
        '''
        初始化token的参数
        '''
        if not header:
            # 如果不指定headers  # 使用默认的headers
            self.header = header = {'alg': 'md5', 'type': 'JWT'}
        self.header = header  # 可以拿到原始的头信息
        # 类方法中使用经过base64编码过的头
        self.token_headers = self._encode_base64(header)
        if not salt:
            # 如果不指定盐  # 使用默认的盐
            salt = settings.SECRET_KEY  # django的密钥
        self.salt = salt.encode(encoding='utf-8')
        self.create_time = time.time()
        self.expired = expired

    @staticmethod
    def _encode_base64(data: dict):
        data_string = json.dumps(data).encode(encoding='utf8')
        return base64.b64encode(data_string)

    @staticmethod
    def _decode_base64(data):
        return base64.b64decode(data)

    def _encrypt_md5(self, data):
        md5 = hashlib.md5()
        md5.update(data + self.salt)
        return md5.hexdigest().encode(encoding='utf8')

    def create_token(self, create_time=None, **kwargs):
        if not create_time:
            create_time = self.create_time
        # 将传递的数据作为载荷创建
        payload = kwargs
        # create_time 因为后续验证的时候需要将老的token时间再拿回来,所以create_time放在最后面,否则校验的时候顺序就会不对
        payload.update({'expired': self.expired, 'create_time': create_time})
        # 将数据进行base64编码
        token_payload = self._encode_base64(payload)
        # 我的headers头是在对象初始化时定义的,这里可以直接调用
        # 签名由 头 + 载荷 + 盐 的数据拼接并使用摘要算法完成构建
        signature = self._encrypt_md5(data=self.token_headers + token_payload)
        # 将 前面再次通过base64进行编码
        signature = base64.b64encode(signature)
        # 使用【.】进行拼接 返回token
        return b'.'.join((self.token_headers, token_payload, signature))  # bytes

    def get_payload_from_token(self, token):
        # 将token按【.】切分  # 分为三个部分
        token_headers, token_payload, token_sign = token.split('.')
        # 中间部分就是携带的荷载
        payload = json.loads(self._decode_base64(token_payload))
        return payload
################### 使用 ##############
# 登录签发token
class UserView(ViewSetMixin, CreateAPIView):
    queryset = UserInfo.objects.all()
    serializer_class = UserInfoSerializer
    authentication_classes = []

    @action(methods=['post'], detail=False)
    def login(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.pop('password')
        user = auth.authenticate(username=username, password=password)
        if not user:
            return Response({'code': 101, 'msg': '用户名或密码错误'})
        token = CommonJWT().create_token(**request.data)
        return Response({'code': 100, 'msg': '登录成功', 'token': token})
############### 认证类校验token ##############
class UserAuthentication(BaseAuthentication):
    def authenticate(self, request):
        jwt = CommonJWT()
        token = request.META.get('HTTP_TOKEN')
        if not token:
            raise AuthenticationFailed("请先登录")
        payload = jwt.get_payload_from_token(token=token)
        create_time = payload.pop('create_time')
        expired = payload.get('expired')
        # user = auth.authenticate(**payload)
        # if not user:
        #     raise AuthenticationFailed('用户名或密码有误')
        username = payload.get('username')
        user = UserInfo.objects.filter(username=username).first()
        if not user:
            raise AuthenticationFailed("token有误")
        if time.time() - create_time > expired:
            raise AuthenticationFailed("当前token已过期")
        true_token = jwt.create_token(create_time=create_time, **payload)
        if token.encode('utf-8') == true_token:
            return user, token
        else:
            raise AuthenticationFailed("请检查token")

标签:username,self,JWT,认证,token,user,data,DRF
From: https://www.cnblogs.com/Lea4ning/p/18162452

相关文章

  • jwt的思路
    我们通常在项目中使用登录接口的时候,会利用jwt的token实现一个对其他接口的一个请求头这层的一个验证,那么如何去应用呢,正常来讲我们需要写出两个功能1.jwt基本的加密和解密2.jwt的一个拦截器,检验token请求头使用依赖jdk1.8仅需<dependency><......
  • keycloak~RequiredActionProvider中获取表单认证前URL的参数
    在keycloak中,我们在进行brower浏览器的表单认证时,一般在跳到本页面时,URL上会有redirect_uri这种参数,用来告诉keycloak,在认证成功后的跳转地址,你在表单认证控制器中,可以通过context.getHttpRequest().getUri().getQueryParameters().getFirst("redirect_uri")进行获取,而当你为browe......
  • PHP身份实名认证接口小白也能轻松实现,ocr接口
    还在为网站用户的身份验证头疼不已?不要慌,今天就带你揭秘如何用PHP语言快速搭建起安全可靠的身份实名认证接口系统!只需对接身份证实名认证接口即可轻松实现用户身份的实名认证。那么,如何用PHP语言实现呢?其实很简单,登录翔云,找到开发者中心,下载所需要的接口开发语言,只需更......
  • SpringSecurity认证授权完整流程
    SpringSecurity认证流程:loadUserByUsername()方法内部实现。实现步骤:   构建一个自定义的service接口,实现SpringSecurity的UserDetailService接口。建一个service实现类,实现此loadUserByUsername方法。调用登录的login接口,会经过authenticationManager.authenticate(authent......
  • DRF之过滤 排序 分页
    DRF之过滤排序分页使用【过滤排序分页】都需要在继承了GenericAPIView的视图类下使用并指定类属性【queryset和serializer_class】【一】过滤#所有过滤类都继承【BaseFilterBackend】fromrest_framework.filtersimportBaseFilterBackend【1】drf自带的过滤#......
  • DRF之三大认证【认证 权限 频率】
    DRF之三大认证【一】三大认证执行顺序【二】认证#我们如果想要自定义认证类,都需要继承【BaseAuthentication】fromrest_framework.authenticationimportBaseAuthentication【1】源码【2】认证类的使用###############################authentications.py#####......
  • 网络实名认证?企业如何实现实名认证?C++身份证实名认证接口
    身份证,大家都不陌生,现如今,我们用到身份证的地方越来越多,人们在办理很多业务时都会要求实名制,比如,电信实名制,火车票,汽车票实名制。为规范网络环境,前段时间国家也出台了相关政策,上网也需要网络实名制,就连大家常用的共享单车在注册时都会要求你实名制,那么,身份证是真是假,是如何确......
  • C++二要素认证,游戏实名认证接口、金融实名认证
    随着移动互联的发展,越来越多的企业在金融市场都想分一杯羹,而哪种理财产品才是人们放心的理财产品呢?我们都知道理财产品开户都需要上传身份证号核验和图像进行验证,以保证个人信息的真实性,那么这些是如何来判别身份证的真伪呢?加入上传一张假的身份证是否也能验证通过呢?翔云身份......
  • DRF之路由组件
    五】路由组件资源路由允许你快速声明给定的有足够控制器的所有公共路由。而不是为你的index...声明单独的路由,一个强大的路由能在一行代码中声明它们。—RubyonRails文档【1】使用SimpleRouter快速生成路由from.viewsimportTaskViewV3#导入模块fromrest_framewor......
  • 凭证管理揭秘:Cookie-Session 与 JWT 方案的对决
    概述在上一篇文章我们聊完了授权的过程,在服务器对客户端完成授权之后,服务器会给客户端颁发对应的凭证,客户端持有该凭证访问服务端,服务器便能知道你是谁,你有什么权限等信息。这一章我们具体聊聊常见的凭证管理技术有哪些。在软件架构中,关于凭证如何存储和传递,一直有两种不同的解......