JWT什麼鬼?django中使用JWT

JWT認證校驗首選

1.pyJWT簡述

  • 因http協議自己爲無狀態,這樣每次用戶發出請求,咱們並不能區分是哪一個用戶發出的請求,這樣咱們能夠經過保存cookie以便於識別是哪一個用戶發來的請求,傳統凡事基於session認證。可是這種認證自己不少缺陷,擴展性差,CSRF等問題。JWT(Json web token) 相比傳統token,設計更爲緊湊且安全。經過JWT能夠實現用戶認證等操做。python

  • pyJWT下載web

    pip install pyJWT
  • JWT構成:算法

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ.eyJ1c2VybmFtZSI6InhqayIsImV4cCI6MTU4MjU0MjAxN30.oHdfcsUftJJob66e5mL1jLRpJwiG0i9MOD5gzM476eY

    jwt是由三段信息構成,將3部分信息構成JWT字符串,經過點進行分割,第一部分稱爲頭部(header),第二部分稱爲在和(payload相似於飛機上承載的物品),第三部分是簽證(signature)。數據庫

  • headerdjango

    • jwt的頭部承載兩部分:聲明類型,聲明加密算法api

      headers = {
              "type":"jwt",
              "alg":"HS256"
          }
    • 而後將頭部進行base64加密。(該加密是能夠對稱解密的),構成了第一部分安全

      eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ
  • playloadrestful

    • 載荷就是存放有效信息的地方,這個名字像是特指飛機上承載的貨品,這些有效信息包含三部分:cookie

      • 標準中註冊聲明(建議不強制使用):session

        • 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 發送帶認證類接口

參考文獻

相關文章
相關標籤/搜索