首页 > 其他分享 >【十一】DRF之JWT认证

【十一】DRF之JWT认证

时间:2023-07-31 12:22:25浏览次数:39  
标签:username JWT jwt 认证 token user import payload DRF

【一】JWT认证

  • 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

  • Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)

    • 该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
  • JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息

    • 以便于从资源服务器获取资源
    • 也可以增加一些额外的其它业务逻辑所必须的声明信息
    • 该token也可直接被用于认证,也可被加密。
  • session的认证

  • 基于session认证下的集群部署

  • 基于jwt的认证

  • 基于jwt认证下的服务器集群部署

【二】构成和工作原理

【1】JWT的构成

  • JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 第一部分我们称它为头部(header)
  • 第二部分我们称其为载荷(payload, 类似于飞机上承载的物品)
  • 第三部分是签证(signature).

【2】头部(header)

  • jwt的头部承载两部分信息:

    • 声明类型,这里是jwt

    • 声明加密的算法 通常直接使用 HMAC SHA256

  • 完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}
  • 然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

【3】载荷(payload)

  • 载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    • 标准中注册的声明

    • 公共的声明

    • 私有的声明

  • 标准中注册的声明 (建议但不强制使用) :

    • iss: jwt签发者

    • sub: jwt所面向的用户

    • aud: 接收jwt的一方

    • exp: jwt的过期时间,这个过期时间必须要大于签发时间

    • nbf: 定义在什么时间之前,该jwt都是不可用的.

    • iat: jwt的签发时间

    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。

  • 公共的声明

    • 公共的声明可以添加任何的信息
    • 一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
  • 私有的声明

    • 私有声明是提供者和消费者所共同定义的声明
    • 一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
  • 定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
  • 然后将其进行base64加密,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

【4】签证(signature)

  • JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)

    • payload (base64后的)

    • secret

  • 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串

  • 然后通过header中声明的加密方式进行加盐secret组合加密

  • 然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 注意:
    • secret是保存在服务器端的
    • jwt的签发生成也是在服务器端的
    • secret就是用来进行jwt的签发和jwt的验证
  • 所以,它就是你服务端的私钥,在任何场景都不应该流露出去。
    • 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
  • 关于签发和核验JWT
    • 我们可以使用Django REST framework JWT扩展来完成。
  • 文档网站:

【5】本质原理

(1)jwt认证算法

"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
  "company": "公司信息",
  ...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
  "user_id": 1,
 ...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
 "head": "头的加密字符串",
  "payload": "体的加密字符串",
 "secret_key": "安全码"
}
"""

(2)jwt认证算法之签发:

根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串

账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""

(3)jwt认证算法之校验:

  • 根据客户端带token的请求 反解出 user 对象
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""

【6】drf项目的jwt认证开发流程(重点)

"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中

2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户

注:登录接口需要做 认证 + 权限 两个局部禁用
"""

【补充】补充base64编码解码

import base64
import json
dic_info={
  "sub": "1234567890",
  "name": "lqz",
  "admin": True
}
byte_info=json.dumps(dic_info).encode('utf-8')

# base64编码
base64_str=base64.b64encode(byte_info)
print(base64_str)

# base64解码
base64_str='eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'
str_url = base64.b64decode(base64_str).decode("utf-8")
print(str_url)

【三】drf-jwt安装和简单使用

【1】官网

http://getblimp.github.io/django-rest-framework-jwt/

【2】安装

pip install djangorestframework-jwt

【3】使用:

1 创建超级用户

python3 manage.py createsuperuser

2 配置路由urls.py

from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]

3 postman测试

向后端接口发送post请求,携带用户名密码,即可看到生成的token

4 setting.py中配置认证使用jwt提供的jsonwebtoken

5 postman发送访问请求(必须带jwt空格)

【四】实战之使用Django auth的User表自动签发

【1】配置setting.py

import datetime
JWT_AUTH = {
    # 过期时间1天
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    # 自定义认证结果:见下方序列化user和自定义response
    # 如果不自定义,返回的格式是固定的,只有token字段
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',  
}

【2】编写序列化类ser.py

from rest_framework import serializers
from users import models
class UserModelSerializers(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = ['username']

【3】自定义认证返回结果(setting中配置的)

#utils.py
from users.ser import UserModelSerializers
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'status': 0,
        'msg': 'ok',
        'data': {
            'token': token,
            'user': UserModelSerializers(user).data
        }
    }

【4】基于drf-jwt的全局认证:

#app_auth.py(自己创建)
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework_jwt.authentication import get_authorization_header,jwt_get_username_from_payload
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication

class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        jwt_value = get_authorization_header(request)

        if not jwt_value:
            raise AuthenticationFailed('Authorization 字段是必须的')
        try:
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('签名过期')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('非法用户')
        user = self.authenticate_credentials(payload)

        return user, jwt_value

【5】全局使用

# setting.py
REST_FRAMEWORK = {
    # 认证模块
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'users.app_auth.JSONWebTokenAuthentication',
    ),
}

【6】局部启用禁用

# 局部禁用
authentication_classes = []
# 局部启用
from user.authentications import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication]
# 实际代码如下view.py
# 自定义Response
class CommonResponse(Response):
    def __init__(self,status,msg,data='',*args,**kwargs):
        dic={'status':status,'msg':msg,'data':data}
        super().__init__(data=dic,*args,**kwargs)
# 测试订单接口
from users.app_auth import JSONWebTokenAuthentication
class OrderView(APIView):
    # authentication_classes = [JSONWebTokenAuthentication]
    authentication_classes = []
    def get(self,request):
        return CommonResponse('100', '成功',{'数据':'测试'})

【7】多方式登录:

  • views.py
## views.py
# 重点:自定义login,完成多方式登录
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class LoginViewSet(ViewSet):
    # 需要和mixins结合使用,继承GenericViewSet,不需要则继承ViewSet
    # 为什么继承视图集,不去继承工具视图或视图基类,因为视图集可以自定义路由映射:
    #       可以做到get映射get,get映射list,还可以做到自定义(灵活)
    def login(self, request, *args, **kwargs):
        serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        token = serializer.context.get('token')
        return Response({"token": token})
  • ser.py
## ser.py
# 重点:自定义login,完成多方式登录
class LoginSerializer(serializers.ModelSerializer):
    # 登录请求,走的是post方法,默认post方法完成的是create入库校验,所以唯一约束的字段,会进行数据库唯一校验,导致逻辑相悖
    # 需要覆盖系统字段,自定义校验规则,就可以避免完成多余的不必要校验,如唯一字段校验
    username = serializers.CharField()
    class Meta:
        model = models.User
        # 结合前台登录布局:采用账号密码登录,或手机密码登录,布局一致,所以不管账号还是手机号,都用username字段提交的
        fields = ('username', 'password')

    def validate(self, attrs):
        # 在全局钩子中,才能提供提供的所需数据,整体校验得到user
        # 再就可以调用签发token算法,将user信息转换为token
        # 将token存放到context属性中,传给外键视图类使用
        user = self._get_user(attrs)
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        self.context['token'] = token
        return attrs

    # 多方式登录
    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        import re
        if re.match(r'^1[3-9][0-9]{9}$', username):
            # 手机登录
            user = models.User.objects.filter(mobile=username, is_active=True).first()
        elif re.match(r'^.+@.+$', username):
            # 邮箱登录
            user = models.User.objects.filter(email=username, is_active=True).first()
        else:
            # 账号登录
            user = models.User.objects.filter(username=username, is_active=True).first()
        if user and user.check_password(password):
            return user

        raise ValidationError({'user': 'user error'})
  • utils.py
# utils.py
import re
from .models import User
from django.contrib.auth.backends import ModelBackend
class JWTModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            if re.match(r'^1[3-9]\d{9}$', username):
                user = User.objects.get(mobile=username)
            else:
                user = User.objects.get(username=username)
        except User.DoesNotExist:
            return None
        if user.check_password(password) and self.user_can_authenticate(user):
            return user

【8】配置多方式登录

settings.py
AUTHENTICATION_BACKENDS = ['user.utils.JWTModelBackend']

【五】实战之自定义User表,手动签发

【1】手动签发JWT:

# 可以拥有原生登录基于Model类user对象签发JWT
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)

【2】编写登陆视图类

# views.py
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from users.models import User
class LoginView(APIView):
    authentication_classes = []
    def post(self,request):
        username=request.data.get('username')
        password=request.data.get('password')
        user=User.objects.filter(username=username,password=password).first()
        if user: # 能查到,登陆成功,手动签发
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return CommonResponse('100','登陆成功',data={'token':token})
        else:
            return CommonResponse('101', '登陆失败')

【3】编写认证组件

# app_auth.py
from users.models import User
class MyJSONWebTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        jwt_value = get_authorization_header(request)

        if not jwt_value:
            raise AuthenticationFailed('Authorization 字段是必须的')
        try:
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('签名过期')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('非法用户')
        username = jwt_get_username_from_payload(payload)
        print(username)
        user = User.objects.filter(username=username).first()
        print(user)

        return user, jwt_value

【4】登陆获取token

【5】编写测试接口

from users.app_auth import JSONWebTokenAuthentication,MyJSONWebTokenAuthentication
class OrderView(APIView):
    # authentication_classes = [JSONWebTokenAuthentication]
    authentication_classes = [MyJSONWebTokenAuthentication]
    def get(self,request):
        print(request.user)
        return CommonResponse('100', '成功',{'数据':'测试'})

【6】测试

标签:username,JWT,jwt,认证,token,user,import,payload,DRF
From: https://www.cnblogs.com/dream-ze/p/17593127.html

相关文章

  • 【十二】DRF之RBAC控制
    【一】什么是RBAC【1】概念RBAC是基于角色的访问控制(Role-BasedAccessControl)在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计......
  • 捷报:斩获32项认证,6大奖项!
    7月25日,由中国信息通信研究院(简称中国信通院)、中国通信标准化协会主办的“2023可信云大会”在京召开。天翼云出席央国企高质量用云创新赋能计划暨《央国企用云白皮书》研究启动仪式、一云多芯应用创新生态社区成立仪式、智算服务提升计划启动仪式暨首批专家聘任仪式、专有云质效验......
  • jwt的安全性问题总结
    jwt生成的token可以被在线解密,那jwt是如何确保安全的呢?Jwt:生成token,token不保存在服务端,服务端只做验证,token中携带用户信息,过期时间等信息。分为3段,其中前两段的信息在没有私匙时可以被获取,因为其只是简单的base64加密,安全的确保在第三段签名,签名是需要私匙来解密的。所以程序......
  • jwt
    JWT基础知识前后端分离的解决方案:什么是JWTJSONWebToken(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。发生在用户和服务器端的一个加密的json格式的字符串。JWT的构成一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与......
  • 02.FreeIPA、FreeRadius双因子认证
    一、安装先决条件Rocky8(2G内存以上,30G硬盘,镜像源修改为阿里云)二、安装FreeIPA修改SeLinux、关闭防火墙、更新系统:timedatectlset-timezoneAsia/Shanghaisetenforce0systemctlstopfirewalld.servicednfupdate安装前准备:设置主机名:hostnamectlset-hostnamef......
  • .NET CORE一步一步实现一个简易JWT鉴权
    鉴权、授权专题之简易鉴权我记得作为实习生去公司上班的时候,领导就直接让我熟悉注册、登录逻辑!!!emm用他的话来说就是这部分跟业务关联性不是很大,你先看看。。。登录,注册是跟业务逻辑不咋挂钩,但是对框架得熟悉呀!好吧,自此咱就开始了搬砖路咯~安装需要的Nuget包1System.IdentityMo......
  • .Net Core中自定义认证实现
    一、起因 (原文)最近项目中需要对项目同时支持JWT认证,以及自定义的认证校验方式认证。通过对官方文档了解,得到认证实现主要通过继承 IAuthenticationHandler 或 AuthenticationHandler<TOptions>来实现自定义认证的处理。那么接下来实现一个自定义的认证访问。二、......
  • .NET Core 6.0 配置自定义JWT鉴权
    JWT授权配置1、安装Microsoft.IdentityModel.Tokens包;2、创建一个认证用户信息实体类UserInfo3、创建一个JWT配置类JWTTokenOptions   publicclassJwtTokenOptions   {       ///<summary>       ///JWT认证Key       ///</sum......
  • express JWT
    jwt(jsonwebtoken)主要是做鉴权用的登录之后存储用户信息下面这段就是生成的token(令牌)eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjg3Njc0NDkyLCJleHAiOjE2ODc3NjA4OTJ9.Y6eFGv4KXqUhlRHglGCESvcJEnyMkMwM1WfICt8xYC4JWT是三部分组成的头部(Header):头部通常由......
  • 获评最高级别权威认证!融云通过中国信通院「办公即时通信软件安全能力」评测
    近期,融云再获权威认可,旗下百幄智能在线办公套件平台正式通过中国信通院“办公即时通信软件安全能力”测评,并获得最高级别“卓越级”证书。关注【融云RongCloud】,了解协同办公平台更多干货。7月27日,“2023数字生态发展大会暨中国信通院‘铸基计划’年中会议”在京举办,融云受邀......