JWT什么鬼?django中使用JWT

Posted xujunkai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JWT什么鬼?django中使用JWT相关的知识,希望对你有一定的参考价值。

JWT认证校验首选

1.pyJWT简述

  • 因http协议本身为无状态,这样每次用户发出请求,我们并不能区分是哪个用户发出的请求,这样我们可以通过保存cookie以便于识别是哪个用户发来的请求,传统凡事基于session认证。但是这种认证本身很多缺陷,扩展性差,CSRF等问题。JWT(Json web token) 相比传统token,设计更为紧凑且安全。通过JWT可以实现用户认证等操作。

  • pyJWT下载

    pip install pyJWT
  • JWT构成:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ.eyJ1c2VybmFtZSI6InhqayIsImV4cCI6MTU4MjU0MjAxN30.oHdfcsUftJJob66e5mL1jLRpJwiG0i9MOD5gzM476eY

    jwt是由三段信息构成,将3部分信息构成JWT字符串,通过点进行分割,第一部分称为头部(header),第二部分称为在和(payload类似于飞机上承载的物品),第三部分是签证(signature)。

  • header

    • jwt的头部承载两部分:声明类型,声明加密算法

      headers = {
              "type":"jwt",
              "alg":"HS256"
          }
    • 然后将头部进行base64加密。(该加密是可以对称解密的),构成了第一部分

      eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ
  • playload

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

      • 标准中注册声明(建议不强制使用):
        • iss:jwt签发者。
        • sub:jwt所面向的用户
        • aud:接收jwt的一方
        • exp:jwt过期时间,这个过期时间必须大于签发时间
        • nbf:定义在什么时间之前,该jwt都是不可用的
        • lat:jwt的签发时间
        • jti:jwt的唯一身份表示,主要用来作为一次性token,从而回避重放攻击。
      • 公共的声明:
        • 可以添加任何信息,一般添加用户相关信息。但不建议添加敏感信息,因为该部分在客户端可解密
      • 私有的声明:

        • 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
        {
            "username": "xjk",
        }
  • signature
    • jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
      • header(base64后的)
      • payload(base64后的)
      • secret
    • 这个部分需要base64加密后的header和base64加密后pyload使用,连接组成的祖父穿,然后通过header中声明加密方式,进行加盐secret组合加密,这样构成第三部分

2.pyJWT在django应用

  • views视图:

    from django.http import JsonResponse
    
    from django.views import View
    from django.views.decorators.csrf import csrf_exempt
    from django.utils.decorators import method_decorator
    from utils.jwt_auth import create_token
    
    # 定义method_decorator 免 csrf校验, dispatch表示所有请求,因为所有请求都先经过dispatch
    @method_decorator(csrf_exempt,name="dispatch")
    class LoginView(View):
        """
        登陆校验
        """
        def post(self,request,*args,**kwargs):
            user = request.POST.get("username")
            pwd = request.POST.get("password")
            # 这里简单写一个账号密码
            if user == "xjk" and pwd == "123":
                # 登陆成功进行校验
                token = create_token({"username":"xjk"})
                # 返回JWT token
                return JsonResponse({"status":True,"token":token})
            return JsonResponse({"status":False,"error":"用户名密码错误"})
    
    # 定义method_decorator 免 csrf校验, dispatch表示所有请求,因为所有请求都先经过dispatch
    @method_decorator(csrf_exempt,name="dispatch")
    class OrderView(View):
        """
        登陆后可以访问
        """
        def get(self, request, *args, **kwargs):
            # 打印用户jwt信息
            print(request.user_info)
            return JsonResponse({'data': '订单列表'})
    
        def post(self, request, *args, **kwargs):
            print(request.user_info)
            return JsonResponse({'data': '添加订单'})
    
        def put(self, request, *args, **kwargs):
            print(request.user_info)
            return JsonResponse({'data': '修改订单'})
    
        def delete(self, request, *args, **kwargs):
            print(request.user_info)
            return JsonResponse({'data': '删除订单'})
    
  • 定于jwt工具 utils/jwt_auth.py

    import jwt
    import datetime
    from jwt import exceptions
    
    
    # 加的盐
    JWT_SALT = "ds()udsjo@jlsdosjf)wjd_#(#)$"
    
    
    def create_token(payload,timeout=20):
        # 声明类型,声明加密算法
        headers = {
            "type":"jwt",
            "alg":"HS256"
        }
        # 设置过期时间
        payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=20)
        result = jwt.encode(payload=payload,key=JWT_SALT,algorithm="HS256",headers=headers).decode("utf-8")
        # 返回加密结果
        return result
    
    
    def parse_payload(token):
        """
        用于解密
        :param token:
        :return:
        """
        result = {"status":False,"data":None,"error":None}
        try:
            # 进行解密
            verified_payload = jwt.decode(token,JWT_SALT,True)
            result["status"] = True
            result['data']=verified_payload
        except exceptions.ExpiredSignatureError:
            result['error'] = 'token已失效'
        except jwt.DecodeError:
            result['error'] = 'token认证失败'
        except jwt.InvalidTokenError:
            result['error'] = '非法的token'
        return result
    
  • 中间件进行jwt校验 middlewares/jwt.py

    class JwtAuthorizationMiddleware(MiddlewareMixin):
        """
        用户需要通过请求头的方式来进行传输token,例如:
        Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU
        """
    
        def process_request(self, request):
    
            # 如果是登录页面,则通过
            if request.path_info == '/login/':
                return
    
            # 非登录页面需要校验token
            authorization = request.META.get('HTTP_AUTHORIZATION', '')
            print(authorization)
            auth = authorization.split()
            # 验证头信息的token信息是否合法
            if not auth:
                return JsonResponse({'error': '未获取到Authorization请求头', 'status': False})
            if auth[0].lower() != 'jwt':
                return JsonResponse({'error': 'Authorization请求头中认证方式错误', 'status': False})
            if len(auth) == 1:
                return JsonResponse({'error': "非法Authorization请求头", 'status': False})
            elif len(auth) > 2:
                return JsonResponse({'error': "非法Authorization请求头", 'status': False})
    
            token = auth[1]
            # 解密
            result = parse_payload(token)
            if not result['status']:
                return JsonResponse(result)
            # 将解密后数据赋值给user_info
            request.user_info = result['data']
    
  • settings注册中间件

    MIDDLEWARE = [
        ...
        'middlewares.jwt.JwtAuthorizationMiddleware',
        ...
    ]
  • 如下结果演示:

    • 首先登陆获取JWT Token

    技术图片

    • 然后拿去JWT Token 添加到 Authorization ,发送给其他路由请求。

    技术图片

3.restframework与pyJWT结合

  • setting.py 要引入restframework

    INSTALLED_APPS = [
        'rest_framework',
    ]
  • 认证类定义:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.authentication import BaseAuthentication
    from rest_framework import exceptions
    from utils.jwt_auth import parse_payload
    
    
    class JwtQueryParamAuthentication(BaseAuthentication):
        """
        用户需要在url中通过参数进行传输token,例如:
        http://www.pythonav.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU
        """
    
        def authenticate(self, request):
            # 从url上获取jwt token
            token = request.query_params.get('token')
            payload = parse_payload(token)
            if not payload['status']:
                raise exceptions.AuthenticationFailed(payload)
    
            # 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。
            return (payload, token)
    
    
    class JwtAuthorizationAuthentication(BaseAuthentication):
        """
        用户需要通过请求头的方式来进行传输token,例如:
        Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU
        """
    
        def authenticate(self, request):
            # 非登录页面需要校验token,从头信息拿去JWT Token
            authorization = request.META.get('HTTP_AUTHORIZATION', '')
            auth = authorization.split()
            if not auth:
                raise exceptions.AuthenticationFailed({'error': '未获取到Authorization请求头', 'status': False})
            if auth[0].lower() != 'jwt':
                raise exceptions.AuthenticationFailed({'error': 'Authorization请求头中认证方式错误', 'status': False})
    
            if len(auth) == 1:
                raise exceptions.AuthenticationFailed({'error': "非法Authorization请求头", 'status': False})
            elif len(auth) > 2:
                raise exceptions.AuthenticationFailed({'error': "非法Authorization请求头", 'status': False})
    
            token = auth[1]
            result = parse_payload(token)
            if not result['status']:
                raise exceptions.AuthenticationFailed(result)
    
            # 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。
            return (result, token)
    
  • view.py使用

    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    from utils.jwt_auth import create_token
    from extensions.auth import JwtQueryParamAuthentication, JwtAuthorizationAuthentication
    
    
    class LoginView(APIView):
        def post(self, request, *args, **kwargs):
            """ 用户登录 """
            user = request.POST.get('username')
            pwd = request.POST.get('password')
    
            # 检测用户和密码是否正确,此处可以在数据进行校验。
            if user == 'xjk' and pwd == '123':
                # 用户名和密码正确,给用户生成token并返回
                token = create_token({'username': 'xjk'})
                return Response({'status': True, 'token': token})
            return Response({'status': False, 'error': '用户名或密码错误'})
    
    
    class OrderView(APIView):
        # 通过url传递token
        authentication_classes = [JwtQueryParamAuthentication, ]
    
        # 通过Authorization请求头传递token
        # authentication_classes = [JwtAuthorizationAuthentication, ]
    
        def get(self, request, *args, **kwargs):
            print(request.user, request.auth)
            return Response({'data': '订单列表'})
    
        def post(self, request, *args, **kwargs):
            print(request.user, request.auth)
            return Response({'data': '添加订单'})
    
        def put(self, request, *args, **kwargs):
            print(request.user, request.auth)
            return Response({'data': '修改订单'})
    
        def delete(self, request, *args, **kwargs):
            print(request.user, request.auth)
            return Response({'data': '删除订单'})
  • 如下结果演示:
    • 首先登陆获取JWT Token

    技术图片

  • 然后拿去JWT Token 添加到url上,发送给其他路由请求。

    技术图片

4. djangorestframework-jwt

  • rest_framework_jwt是封装jwt符合restful规范接口

  • 安装:

    pip install djangorestframework-jwt

演示前必须做一些操作

  • settings.py配置

    INSTALLED_APPS = [
        ...
        'rest_framework'
    ]
    
    
    import datetime
    #超时时间
    JWT_AUTHTIME = {
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
        # token前缀
        'JWT_AUTH_HEADER_PREFIX': 'JWT',
    }
    
    # 引用Django自带的User表,继承使用时需要设置
    AUTH_USER_MODEL = 'api.User'
  • models.py建立表

    from django.db import models
    
    # Create your models here.
    from django.contrib.auth.models import AbstractUser
    
    class User(AbstractUser):
        CHOICE_GENDER = (
            (1,"男"),
            (2,"女"),
            (3,"不详"),
        )
        gender = models.IntegerField(choices=CHOICE_GENDER,null=True,blank=True)
        class Meta:
            db_table = "user"
    
  • 定义一个路由创建一个用户

    urlpatterns = [
        url(r'^reg/', views.RegView.as_view()),
    ]
  • 创建注册用户视图:

class RegView(APIView):
    def post(self,request,*args,**kwargs):
        receive = request.data
        username = receive.get("username")
        password = receive.get("password")
        user = User.objects.create_user(
            username=username, password=password
        )
        user.save()
        return Response({"code":200,"msg":"ok"})
  • 访问/reg 发送post请求创建用户

开始使用jwt

  • 在url添加登陆路由

    from django.conf.urls import url
    from django.contrib import admin
    from rest_framework_jwt.views import obtain_jwt_token
    from api import views
    urlpatterns = [
        # 登入验证,使用JWT的模块,只要用户密码正确会自动生成一个token返回
        url(r'^login/', obtain_jwt_token),
        # 访问带认证接口
        url(r'^home/', views.Home.as_view()),
    ]
  • 访问login/

    技术图片

  • 定义认证视图:

    class Home(APIView):
        authentication_classes = [JwtAuthorizationAuthentication]
        def get(self,request,*args,**kwargs):
            return Response({"code":200,"msg":"this is home"})
    
  • 定义认证类JwtAuthorizationAuthentication:

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
    
    class JwtAuthorizationAuthentication(BaseAuthentication):
        def authenticate(self, request):
            # 获取头信息token
            authorization = request.META.get('HTTP_AUTHORIZATION', '')
            print(authorization)
            # 校验
            valid_data = VerifyJSONWebTokenSerializer().validate({"token":authorization})
            """
            valid_data = {'token': '太长了省略一下...'
            'user': <User: xjk>
            }
            """
            user = valid_data.get("user")
            if user:
                return
            else:
                raise AuthenticationFailed("认证失败了。。。")
    
  • 复制token,放在AUTHORIZATION 发送带认证类接口

    技术图片

参考文献

以上是关于JWT什么鬼?django中使用JWT的主要内容,如果未能解决你的问题,请参考以下文章

使用 Django rest_framework_jwt,如何允许使用准时使用代码自动登录?

Django JWT 身份验证 - 用户在中间件中是匿名的

Django REST JWT 刷新

在 Django/DRF 中使用 JWT 身份验证并将 JWT 存储在 HttpOnly Cookie 中

在 Django 中使用 JWT 进行身份验证

在 Django 中使用 JWT 令牌进行身份验证