在用戶註冊或登陸後,咱們想記錄用戶的登陸狀態,或者爲用戶建立身份認證的憑證。咱們再也不使用Session認證機制,而使用Json Web Token認證機制。javascript
Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。前端
JWT就一段字符串,由三段信息構成的,將這三段信息文本用.
連接一塊兒就構成了Jwt字符串。就像這樣:java
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature).git
jwt的頭部承載兩部分信息:github
聲明類型,這裏是jwtweb
聲明加密的算法 一般直接使用 HMAC SHA256算法
完整的頭部就像下面這樣的JSON:django
{ 'typ': 'JWT', 'alg': 'HS256' }
而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分.後端
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分api
標準中註冊的聲明
公共的聲明
私有的聲明
標準中註冊的聲明 (建議但不強制使用) :
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過時時間,這個過時時間必需要大於簽發時間
nbf: 定義在什麼時間以前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。
公共的聲明 : 公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密.
私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
定義一個payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
而後將其進行base64加密,獲得JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:
header (base64後的)
payload (base64後的)
secret
這個部分須要base64加密後的header和base64加密後的payload使用.
鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret
組合加密,而後就構成了jwt的第三部分。
// javascript若是要模擬生成你的jwttoken,可能能夠採用如下代碼生成[注意:僞代碼] var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
將這三部分用.
鏈接成一個完整的字符串,構成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,因此,它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。
關於簽發和核驗JWT,咱們可使用Django REST framework JWT擴展來完成。
文檔網站http://getblimp.github.io/django-rest-framework-jwt/
1.2.1 安裝
pip install djangorestframework-jwt
1.2.2 setting中的配置文件
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), } import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), }
注:JWT_EXPIRATION_DELTA 指明token的有效期
Django REST framework JWT 擴展的說明文檔中提供了手動簽發JWT的方法
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)
在用戶註冊或登陸成功後,在序列化器中返回用戶信息之後同時返回token便可。
在子應用users路由urls.py中
from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path(r'authorizations/', obtain_jwt_token, name='authorizations'), ]
接下來,咱們能夠經過postman來測試下功能
咱們能夠將JWT保存在cookie中,也能夠保存在瀏覽器的本地存儲裏,咱們保存在瀏覽器本地存儲中
瀏覽器的本地存儲提供了sessionStorage 和 localStorage 兩種:
sessionStorage 瀏覽器關閉即失效
localStorage 長期有效
使用方法
sessionStorage.變量名 = 變量值 // 保存數據 sessionStorage.變量名 // 讀取數據 sessionStorage.clear() // 清除全部sessionStorage保存的數據 localStorage.變量名 = 變量值 // 保存數據 localStorage.變量名 // 讀取數據 localStorage.clear() // 清除全部localStorage保存的數據
JWT擴展的登陸視圖,在收到用戶名與密碼時,也是調用Django的認證系統中提供的authenticate()來檢查用戶名與密碼是否正確。
咱們能夠經過修改Django認證系統的認證後端(主要是authenticate方法)來支持登陸帳號既能夠是用戶名也能夠是手機號。
修改Django認證系統的認證後端須要繼承django.contrib.auth.backends.ModelBackend,並重寫authenticate方法。
authenticate(self, request, username=None, password=None, **kwargs)
方法的參數說明:
request 本次認證的請求對象
username 本次認證提供的用戶帳號
password 本次認證提供的密碼
咱們想要讓用戶既能夠以用戶名登陸,也能夠以手機號登陸,那麼對於authenticate方法而言,username參數即表示用戶名或者手機號。
重寫authenticate方法的思路:
根據username參數查找用戶User對象,username參數多是用戶名,也多是手機號
若查找到User對象,調用User對象的 check_password 方法檢查密碼是否正確
在users/utils.py中編寫:
from django.contrib.auth.backends import ModelBackend from .models import User from django.db.models import Q import re def get_user_by_account(account): """根據帳號信息獲取用戶模型""" try: # if re.match('^1[3-9]\d{9}$', account): # # 手機號 # user = User.objects.get(mobile=account) # else: # # 用戶名 # user = User.objects.get(username=account) user = User.objects.get(Q(mobile=account) | Q(username=account)) except User.DoesNotExist: user = None return user class UsernameMobileAuthBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): # 進行登陸判斷 user = get_user_by_account(username) # 帳號經過了還要進行密碼的驗證,以及判斷當前站好是不是激活狀態 if isinstance(user,User) and user.check_password(password) and self.user_can_authenticate(user): return user
在配置文件settings.py中告知Django使用咱們自定義的認證後端
AUTHENTICATION_BACKENDS = [ 'users.utils.UsernameMobileAuthBackend', ]
ok