這裏真的很簡單的提一下認證的發展歷程。之前大都是採用cookie、session的形式來進行客戶端的認證,帶來的結果就是在數據庫上大量存儲session致使數據庫壓力增大,大體流程以下:前端
在該場景下,分佈式、集羣、緩存數據庫應運而生,認證的過程大體以下:git
不過該方式仍是緩解不了數據庫壓力,一個項目中應該儘量多的減小IO操做,因而後來採用簽名的方式,在服務端只保存token的簽名算法,當客戶端認證時,只需用算法去生成或是判斷token的合法性便可。大體方式以下:github
Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519)。該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。web
JWT由三部分組成,分別是頭.載荷.簽名(header.payload.signature),格式以下:算法
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im93ZW4iLCJleHAiOjE1NTgzMDM1NDR9
.4j5QypLwufjpqoScwUB9LYiuhYcTw1y4dPrvnv7DUyo
其中JWT的頭和載荷均採用base64加密,是能夠被反解的,主要用於反解以後提取用戶信息、過時時間等。而簽名部分採用摘要算法SHA256不可逆加密,裏面摻雜了密鑰,密鑰是存儲於Django中的固定字符串,主要用於認證token。數據庫
jwt = base64(頭部).base64(載荷).hash256(base64(頭部).base(載荷).密鑰)
JWT官網:https://github.com/jpadilla/django-rest-framework-jwtdjango
首先,django-jwt提供了一個生成token的接口,先安裝django-jwt模塊:api
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