一. 認證的發展歷程簡介
這里真的很簡單的提一下認證的發展歷程。以前大都是采用cookie、session的形式來進行客戶端的認證,帶來的結果就是在數據庫上大量存儲session導致數據庫壓力增大,大致流程如下:
在該場景下,分布式、集群、緩存數據庫應運而生,認證的過程大致如下:
不過該方式還是緩解不了數據庫壓力,一個項目中應該盡可能多的減少IO操作,於是后來采用簽名的方式,在服務端只保存token的簽名算法,當客戶端認證時,只需用算法去生成或是判斷token的合法性即可。大致方式如下:
二. JWT簽發Token源碼分析
2.1 JWT工作原理及簡介
Json web token (JWT), 是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標准((RFC 7519)。該token被設計為緊湊且安全的,特別適用於分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
JWT由三部分組成,分別是頭.載荷.簽名(header.payload.signature),格式如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im93ZW4iLCJleHAiOjE1NTgzMDM1NDR9
.4j5QypLwufjpqoScwUB9LYiuhYcTw1y4dPrvnv7DUyo
- header:一般存放如何處理token的方式:加密的算法、是否有簽名等
- payload:數據的主體部分:用戶信息、發行者、過期時間等
- signature:簽名:將header、payload再結合密碼鹽整體處理一下
其中JWT的頭和載荷均采用base64加密,是可以被反解的,主要用於反解之后提取用戶信息、過期時間等。而簽名部分采用摘要算法SHA256不可逆加密,里面摻雜了密鑰,密鑰是存儲於Django中的固定字符串,主要用於認證token。
jwt = base64(頭部).base64(載荷).hash256(base64(頭部).base(載荷).密鑰)
JWT官網:https://github.com/jpadilla/django-rest-framework-jwt
2.2 JWT生成token源碼分析
首先,django-jwt提供了一個生成token的接口,先安裝django-jwt模塊:
pip install djangorestframework-jwt
首先看看jwt的視圖views.py:
因為是CBV,通過類名.as_view()調用視圖類,而jwt中已經有加了as_view的屬性:
所以在路由urls.py中,我們只需導入該屬性即可(path是django2.x版本的語法,不是正則匹配):
from django.urls import path from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('get_token/', obtain_jwt_token) ]
因為生成token是在用戶登錄的時候,所以是post請求,依據屬性查找順序找到post方法:
看看post方法的詳情:
首先看看get_serilizer方法:
get_serializer_class方法:
生成token的類中的serializer_class:
所以post方法中的get_serializer方法返回的是JSONWebTokenSerializer的對象:
其中username_field是封裝的方法,PasswordField一看就是類:
還有USERNAME_FIELD默認就叫username:
由上述可以得知get_serializer方法返回的是一個反序列化后的對象,而且該序列化還提供了全局鈎子。
現在post方法接着往下走,讓我們看看其中的jwt_response_payload_handler方法,依據數據的查找順序:
點擊api_settings中,查找JWT_RESPONSE_PAYLOAD_HANDLER:
所以要讓它采用自定義的函數時,只需要在項目中的settings.py中加Jwt-AUTH字典,然后在里面填寫相應配置即可。
那么看看jwt_response_payload_handler:
可以重寫該方法增加返回給前端的值,同時需要在配置文件中配置JWT_RESPONSE_PAYLOAD_HANDLER:
from .serializers import UserModelSerializers def jwt_response_payload_handler(token, user=None, request=None): return { 'token': token, 'user': UserModelSerializer(user).data } # restful 規范 # return { # 'status': 0, # 'msg': 'OK', # 'data': { # 'token': token, # 'username': user.username # } # }
配置文件中添加以下配置:
JWT_AUTH = { # 自定義認證結果:見下方序列化user和自定義response 'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler', }
走到這里,發現token已經有了,那么是在哪一步生成的呢?沒錯,就是當序列化通過時,執行is_valid時會執行序列化類的鈎子函數,而token就是在全局鈎子函數中生成的:
先看看all(credentials.values())方法:
再往下走authenticate方法,其中的credentials就是用戶名與密碼:
看看_get_backends(return_tuples=True)方法:
首先點擊因為settings是django.conf中的,我們直接點擊通過最上方導入的conf點擊過去,然后通過屬性的查找順序,settings對象是先使用我們項目中的settings.pu配置,其次是django自帶的global_settings.py中的配置,依據屬性查找順序,我們找到global_settings.py中的AUTHENTICATION_BACKENDS:
再看看load_backend方法里的import_string(path)():
所以authenticate(request=None, **credentials)方法中的backend.authenticate(request, **credentials)方法實際上就是ModelBackend.authenticate方法:
這里意味着我們可以重寫ModelBackend的authenticate實現多方式登錄,因為只需要最終返回一個user對象或者None就行:

from django.contrib.auth.backends import ModelBackend from .models import User import re class JWTModelBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): """ :param request: :param username: 前台傳入的用戶名 :param password: 前台傳入的密碼 :param kwargs: :return: """ try: if re.match(r'^1[3-9]\d{9}$', username): user = User.objects.get(mobile=username) elif re.match(r'.*@.*', username): user = User.objects.get(email=username) else: user = User.objects.get(username=username) except User.DoesNotExist: return None # 認證失敗就返回None即可,jwt就無法刪除token # 用戶存在,密碼校驗通過,是活着的用戶 is_active字段為1 if user and user.check_password(password) and self.user_can_authenticate(user): return user # 認證通過返回用戶,交給jwt生成token
回到我們的序列化類JSONWebTokenSerializer中的全局鈎子:
先來看看jwt_payload_handler(user)方法:
點擊跳轉至api_settings:
找到這兩個方法,先來看一下jwt_payload_handler:
接下來看看jwt_encode_handler(payload)方法:
上述的jwt.encode就是生成token的方法接下來看看他里面的幾個參數。
設置中JWT_PRIVATE_KEY值為None,所以會走jwt_get_secret_key:
因為默認配置中JWT_GET_USER_SECRET_KEY值也為None,所以直接返回JWT_SECRET_KEY:
最終返回的是django項目setting.py中的SECRET_KEY,是一串無序的字符串,用於JWT的簽名。
走到這里,JWT生成token的源碼差不多都走一遍了。然后我們可以在views.py中導入jwt_payload_handler和jwt_encode_handler實現手動簽發token。
from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) # 了解,原生視圖 # 原生APIView可以實現手動簽發 jwt class LoginAPIView(APIView): def post(self): # 完成手動簽發 pass