django drf jwt_jwt验证原理

django drf jwt_jwt验证原理前言带着问题学习是最有目的性的,我们先提出以下几个问题,看看通过这篇博客的讲解,能解决问题吗?什么是JWT?为什么要用JWT?它有什么优势?JWT的认证流程是怎样的?JWT的工作原理?我们

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

前言

带着问题学习是最有目的性的,我们先提出以下几个问题,看看通过这篇博客的讲解,能解决问题吗?

  1. 什么是JWT?
  2. 为什么要用JWT?它有什么优势?
  3. JWT的认证流程是怎样的?
  4. JWT的工作原理?

我们带着4个问题进入学习
 

1.什么是JWT?

JWT全称Json Web Token,JWT 是一种开发的行业标准 RFC 7519 ,用于安全的表示双方之间的声明。目前,JWT广泛应用在系统的用户认证方面,特别是现在前后端分离项目。
 

2.为什么要使用JWT?它有什么优势?

用户登录认证方式分为传统的token登录方式和JWT 方式,传统的方式又分为session登录和缓存登录
 

2.1 session登录

"""
接收到登录请求, 1.得到用户 2.产生token 3.记录到session表 4.返回token

接收需要认证信息的请求, 1.拿到token 2.数据库校验 3.确定登录用户 4.返回认证后信息           与数据库session表交互
"""

 

2.2 缓存登录

"""
接收到登录请求, 1.得到用户 2.产生token 3.记录到缓存 4.返回token

接收需要认证信息的请求, 1.拿到token 2.缓存校验 3.确定登录用户 4.返回认证后信息            用户登录信息缓存存储
"""

 

2.3 JWT方式

"""
接收到登录请求, 1.得到用户 2.根据用户产生有用户信息的token 3.返回token

接收需要认证信息的请求, 1.拿到token 2.检验token是否合法,校验出用户 3.返回认证后信息
"""

 

2.4 JWT优点

  1. 服务器不需要存储tokentoken交给每一个客户端自己存储,服务器压力小
  2. 服务器存储的是 签发和校验token两段算法,签发认证的效率高
  3. 算法完成各集群服务器同步成本低,路由项目完成集群部署(适应高并发)
     

2.5 JWT特点

  1. token一定在服务器产生,且在服务器校验
  2. token一定参与网络传输
  3. token携带的信息存在能被反解不能被反解的多部分组成
     

3.JWT组成以及加密原理

JWT是由头部header、载荷payload、签名signature,三段式组成,用.进行拼接,例如官网的这段字符串

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

每一部分都是一个json字典加密形参的字符串,头部和载荷采用的是base64url加密(前台后台都可以解密),签名采用hash256不可逆加密
注意:base64url加密是先做base64加密,然后再将字符串中的 - 替代 + _替代 /
 

3.1 Header

头部包含了两部分,token 类型和采用的加密算法

{
  "alg": "HS256",
  "typ": "JWT"
}
  • typ: (Type)类型,指明类型是JWT
  • alg: (Algorithm)算法,必须是JWS支持的算法,主要是HS256RS256

它会使用 base64url编码组成 JWT 结构的第一部分
 

3.2 Payload

这部分就是我们存放信息的地方了,你可以把用户ID等信息放在这里,JWT规范里面对这部分有进行了比较详细的介绍,JWT 规定了7个官方字段,供选用

  • iss (issuer):签发人
  • exp (expiration time):过期时间,时间戳
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间,时间戳
  • iat (Issued At):签发时间,时间戳
  • jti (JWT ID):编号

常用的有issiatexpaudsub

{
  "sub": "1234567890",
  "name": "John Doe",
  "id": 1,
  "iat": 1516239022
}

同样的,它会使用base64url编码组成 JWT 结构的第二部分
 

3.3 Signature

前面两部分都是使用base64url进行编码的,前端可以解开知道里面的信息。Signature需要使用编码后的 headerpayload 以及我们提供的一个密钥,这个密钥只有服务器才知道,不能泄露给用户,然后使用 header 中指定的签名算法(HS256)进行签名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 HeaderPayloadSignature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

签名的目的
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
 

4.解密原理

1.对token进行切割
2.对第二段进行base64url解密,并获取payload信息,检测exp是否过期
3.将第1,2部分密文拼接起来,再次执行HS256加密
将加密后的密文 = base64解密(第三段字符串)
如果相等则通过,不相等则失败
 

5.JWT的使用方式

  客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage
  此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求的头信息Authorization字段里面。
django drf jwt_jwt验证原理

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。

  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同aaa.bbb.ccc的字符串。

  3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStoragesessionStorage上,退出登录时前端删除保存的JWT即可。

  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSSXSRF问题)

  5. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
     

6.JWT代码演示

首先我们需要安装JWT

pip3 install PyJWT==1.7.1

然后创建一个新的文件jwt_auth,名字随便取,写一个签发token的方法和校验token的方法

import datetime
import jwt

salt = "iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv="


def create_token():
    """
    自定义token
    """
    # 过期时间
    expire_time = datetime.datetime.utcnow() + datetime.timedelta(days=7)
    # 构造headers
    headers = {
        'typ': 'jwt',
        'alg': 'HS256'
    }
    # 构造payload
    payload = {
        "userId": 1,
        "exp": expire_time
    }
    result = jwt.encode(payload=payload, key=salt, algorithm="HS256", headers=headers).decode("utf-8")
    return result


def parse_payload(token):
    """
    对token进行校验并获取payload
    """
    try:
        verified_payload = jwt.decode(token, key=salt)
        return verified_payload
    except jwt.ExpiredSignatureError:
        print('token已失效')
    except jwt.DecodeError:
        print('token认证失败')
    except jwt.InvalidTokenError:
        print('非法的token')


if __name__ == '__main__':
    token = create_token()
    print(token)
    print(parse_payload(token))

  我们上面写了一个创建token的方法和校验token的方法,然后我们执行这个脚本,结果如下

token:eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImV4cCI6MTYyNDYwOTAzNX0.VyjHR6xn94nImEsaIqVE_g84WY_88XuzVHhbqEk-XbM
校验结果:{'userId': 1, 'exp': 1624609035}

  可以看到,我们可以正常签发和校验token了,实际开发过程中,我们会把salt换成settings.py文件下的SECRET_KEY,然后把useId不要写死换成user.pk即可
 

7.djangorestframework-jwt

  以上我们都是使用的PyJWT,而DRF有个第三方库djangorestframework-jwt,帮我们更加方便的使用JWT,它是基于PyJWT==1.7.1进行再次封装的。最新的官网(http://jpadilla.github.io/django-rest-framework-jwt/)
 

7.1安装命令

pip3 install djangorestframework-jwt

 

8.实战案例

我们做一个用户登录的需求,用户登录可以使用以下3种方式

  • 账号密码登录
  • 手机号密码登录
  • 邮箱密码登录

且需要自己自定义JWT认证,认证的格式为header请求头中的AUTHORIZATION字段的值为jwt token的形式,然后后端取出token,通过算法检查出token是否合法
 

8.1前置准备工作

创建项目jwt_demo,然后创建个app名字为api,接着配置好数据库,然后在models.py文件中创建MyUser模型

from django.db import models
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
    phone = models.CharField(verbose_name='手机号码', max_length=11, null=True, unique=True)

这样User表中就有了phone字段,并且在settings.py文件中设置默认User模型AUTH_USER_MODEL = "api.MyUser"
接着在api中创建serializers.py文件,编写如下序列化

from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings


User = get_user_model()  # 获取用户模型


class LoginSerializer(serializers.ModelSerializer):
    # 设置自定义的反序列化字段usr,pwd
    usr = serializers.CharField(write_only=True)
    pwd = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['username', 'email', 'phone', 'usr', 'pwd']
        extra_kwargs = {
            "username": {
                "read_only": True
            },
            "email": {
                "read_only": True
            },
            "phone": {
                "read_only": True
            }
        }

  我们在序列化的时候,让前台传的字段不再是User表中的username这些,而是自定义的usrpwdusr字段的值可以是用户名或邮箱或手机号,这样一来就实现了3种登录方式
 
编写完序列化类,我们来完成视图的工作

import re
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
from rest_framework.views import APIView
from rest_framework_jwt.settings import api_settings
from api.utils.response import APIResponse


User = get_user_model()


class LoginView(APIView):
    """
    登陆视图,用户名与密码匹配返回token
    """
    authentication_classes = []
    permission_classes = []

    def post(self, request, *args, **kwargs):
        try:
            # 获取前台穿的usr和pwd字段
            usr = request.data.get("usr")
            pwd = request.data.get("pwd")
        except KeyError:
            return APIResponse(data_status=10002, data_msg="请求数据非法")
        if re.match(r"1[35678]\d{9}", usr):
            # 正则匹配手机号
            user = User.objects.filter(phone=usr).first()
        elif re.match(r'^[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}\.[com,cn,net]{1,3}$', usr):
            # 正则匹配邮箱
            user = User.objects.filter(email=usr).first()
        else:
            # 用户名
            user = User.objects.filter(username=usr).first()
        if not user:
            return APIResponse(data_status=10002, data_msg="该用户未注册")
        if user.is_active == 0:
            return APIResponse(data_status=10002, data_msg="用户被禁用")
        if not check_password(pwd, user.password):
            return APIResponse(data_status=10002, data_msg="用户名或密码错误")
        
        # 调用第三方的JWT_PAYLOAD_HANDLER和JWT_ENCODE_HANDLER,这里也可以自定义该方法
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        
        # 通过user解析出payload
        payload = jwt_payload_handler(user)
        # 通过payload生成token
        token = jwt_encode_handler(payload)
        return APIResponse(token=token, results={"user": user.username})


class TestView(APIView):
    def get(self, request, *args, **kwargs):
        return APIResponse()

视图中我们创建了2个类视图,第一个是登录视图,用来登录后返回token,第二个类视图是为了测试登录成功后,以后访问视图都需要在请求头中携带token,否则权限验证失败。
最后我们配置路由即可

urlpatterns = [
    path('login/', views.LoginView.as_view()),
    path('test/', views.TestView.as_view())
]

 

8.2自定义权限验证

我们创建一个auth.py文件,编写自定义权限

import jwt
from django.contrib.auth import get_user_model
from rest_framework.authentication import get_authorization_header
from rest_framework_jwt.authentication import jwt_decode_handler, BaseJSONWebTokenAuthentication, \
    jwt_get_username_from_payload
from rest_framework import exceptions


User = get_user_model()  # 获取用户模型


class JWTAuthentication(BaseJSONWebTokenAuthentication):
    keyword = "JWT"

    def authenticate(self, request):
        # 获取请求头字符串,分割成列表
        auth = get_authorization_header(request).split()
        if not auth:
            msg = "未获取到Authorization请求头"
            raise exceptions.AuthenticationFailed(msg)
        if auth[0].lower() != self.keyword.lower().encode():
            msg = "Authorization请求头中认证方式错误"
            raise exceptions.AuthenticationFailed(msg)
        if len(auth) == 1:
            msg = "非法Authorization请求头"
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            raise exceptions.AuthenticationFailed({"message": "无效的授权头。凭据字符串''不应包含空格"})
        try:
            jwt_token = auth[1]
            payload = jwt_decode_handler(jwt_token)
        except jwt.ExpiredSignature:
            msg = 'token已失效'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = '签名解析失败'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        user = self.authenticate_credentials(payload)

        return user, jwt_token

    def authenticate_credentials(self, payload):
        """
        Returns an active user that matches the payload's user id and email.
        """
        User = get_user_model()
        username = jwt_get_username_from_payload(payload)

        if not username:
            msg = _('Invalid payload.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            user = User.objects.get_by_natural_key(username)
        except User.DoesNotExist:
            msg = '用户不存在'
            raise exceptions.AuthenticationFailed(msg)

        if not user.is_active:
            msg = '用户已禁用'
            raise exceptions.AuthenticationFailed(msg)

        return user

最后我们在settings.py文件中配置下即可

REST_FRAMEWORK = {
    # 自定义的认证类
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'api.auth.JWTAuthentication',
    ),
    # 使用drf的权限验证
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

JWT_AUTH = {
    # token的过期时间设置,默认是5分钟过期
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
}

最后我们通过python manage createsuperuser,创建超级用户,usernameadmin,’password’为admin123phone13345678901email100100100@qq.com
 

9.测试自定义的token权限

我们使用apifox工具进行接口测试,首先使用post请求访问http://127.0.0.1:8000/api/login/

9.1手机号登录

django drf jwt_jwt验证原理
 

9.2邮箱登录

django drf jwt_jwt验证原理
 

9.3账号密码登录

django drf jwt_jwt验证原理
 

9.4携带token登录

登录成功后,我们拿着token去访问视图,我们在header中添加AUTHORIZATION字段
django drf jwt_jwt验证原理
我们发现是可以登录成功的,最后如果你想验证过期时间,你可以把token中的第二段字符串,使用base64解密,就能看到时间戳

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/166119.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • open 函数[通俗易懂]

    open 函数[通俗易懂]open函数用来打开一个文件open返回值为一个文件句柄,从操作系统托付给你的python程序,一旦处理完文件,需要归还句柄,只有这样你的程序不会超过一次能打开的文件句柄的数量上限withopen(‘photo.jpg’,’r+’)asf: jpgdata=f.read()open的第⼀个参数是⽂件名。第⼆个(mode打开模式)决定了这个⽂件如何被打开。如果你想读…

    2022年5月25日
    42
  • java怎样调用oracle存储函数_oracle如何调用存储过程

    java怎样调用oracle存储函数_oracle如何调用存储过程之前给大家介绍了java代码调用存储过程,下面要给大家介绍的就是java当中调用oracle存储过程,一起来看看吧。首先来看一下项目结构:在数据库创建存储过程的脚本,假如,使用的是本地的oracle数据库,那么,就需要开启服务-OracleOraDb11g_home1TNSListener和OracleServiceORCL。实现:输入用户的工号,输出用户名字、薪水以及工作:createorr…

    2022年10月20日
    2
  • idea设置快捷键为eclipse_idea设置上一步下一步快捷键

    idea设置快捷键为eclipse_idea设置上一步下一步快捷键一、点击file中的setting(也可以使用快捷键:ctrl+alt+s)settings界面如下:选择keymap选项卡二、设置快捷键1.选择一整套已有快捷键在右侧的keymap下拉框中选择合适自己的快捷键,如eclipse等2.单独设置某个快捷键可以在搜索框中通过名称搜索快捷键内容如添加:还可以通过已有快捷键进行搜索…

    2022年9月20日
    3
  • Idea激活码最新教程2022.2版本,永久有效激活码,亲测可用,记得收藏

    Idea激活码最新教程2022.2版本,永久有效激活码,亲测可用,记得收藏Idea 激活码教程永久有效 2022 2 激活码教程 Windows 版永久激活 持续更新 Idea 激活码 2022 2 成功激活

    2025年5月25日
    3
  • 控制台打印图形_前端控制台打印

    控制台打印图形_前端控制台打印问题描述一、在控制台输出以星号打印的三角形思路:在外部使用循环语句执行5次每次打印1行,每行的内容分别为空格和星号,每行空格缩进的数量为5减去所在行数,星号的数量是所在行数的2倍减1。在内部使用循环语句首先打印空格,然后打印星号”*”,对应的打印次数用循环次数控制,打印星号之后就可以换行。publicstaticvoidmain(String[]args){ //打印图形, intn=5;//表示要打印几行 for(inti=1;i<=n;i++){//i表示每行

    2022年10月20日
    3
  • malloc函数实现原理!

    malloc函数实现原理!任何一个用过或学过C的人对malloc都不会陌生。大家都知道malloc可以分配一段连续的内存空间,并且在不再使用时可以通过free释放掉。但是,许多程序员对malloc背后的事情并不熟悉,许多人甚至把malloc当做操作系统所提供的系统调用或C的关键字。实际上,malloc只是C的标准库中提供的一个普通函数,而且实现malloc的基本思想并不复杂,任何一个对C和操作系统有些许了解的程序员都可以很

    2022年5月31日
    42

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号