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組合加密,這樣構成第三部分
- jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:
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
發送帶認證類接口