zoukankan      html  css  js  c++  java
  • 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 发送带认证类接口

    参考文献

  • 相关阅读:
    【游学】Our trip in Baidu developer conference~
    【招新】Bit Workshop ,requiring new ~Welcome ~
    【web】Modify some style of Diandian blog
    【随谈】The little words is essence~
    【电脑】Enable administrator account in win 7
    【web】What's the hell of diandian ?Why can't recognize the videos ?
    【游学】Fortunately ,photographed with the COO of dolphin browser ,Mr.Wang,and the general mangager of Demo coffee Mr.Yan
    【电脑】Modify the default system guide in win 7
    sql中while遍历更新字段数据
    mysql报ERROR [42000]
  • 原文地址:https://www.cnblogs.com/xujunkai/p/12359573.html
Copyright © 2011-2022 走看看