JWT解析
jwt : Json web token,一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
傳統session認證
http協議是一種無狀態、短連接的協議,而這就意味着如果用戶向應用提供了用戶名和密碼來進行用戶認證,那么下一次請求時,用戶還要再一次進行用戶認證才行,因為根據http協議,我們並不能知道是哪個用戶發出的請求,所以為了應用能識別是哪個用戶發出的請求,我們只能在服務器存儲一份用戶登錄的信息,也就是session。
這份登錄信息session會在響應時傳遞給瀏覽器,保存在cookie,以便下次請求攜帶session,這樣就能夠識別請求來自哪個用戶了,這就是傳統的基於session認證。
基於session認證暴露的問題
- 每個用戶經過認證之后,都需要保存一個session, 如果是保存在內存中,而隨着認證用戶的增多,服務端的開銷會明顯增大。
- 擴展性:如果認證的記錄被保存在內存中的話,這意味着用戶下次請求還必須要請求在這台服務器上,這樣才能拿到授權的資源,這樣在分布式的應用上,相應的限制了負載均衡器的能力。這也意味着限制了應用的擴展能力。
- CSRF: cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。
基於token的認證
不需要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不需要去考慮用戶在哪一台服務器登錄了,這就有了擴展性、安全性。
流程:
- 用戶使用用戶名密碼來請求服務器
- 服務器進行驗證用戶的信息
- 服務器通過驗證發送給用戶一個token
- 客戶端存儲token,並在每次請求時攜帶上這個token值
- 服務端驗證token值,並返回數據
token必須要在每次請求時傳遞給服務端,它應該保存在請求頭里, 另外,服務端要支持CORS(跨來源資源共享)
策略,一般我們在服務端這么做就可以了Access-Control-Allow-Origin: *
。
JWT:
jwt格式
JWT是由三段信息構成的,將這三段信息文本用.
鏈接一起就構成了Jwt字符串。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
結構:
header: {"typ":'JWT',"alg":'HS256'}
payload: {"user_id":1, 'username':'xx', 'exp':'超時時間'}
signature: 前兩部分加密后拼接,再加密
-
頭部header:{'typ':'JWT',"alg":'HS256'}
- 聲明類型,jwt
- 聲明算法,hs256
然后將頭部進行Base64ulr加密,構成第一部分
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
-
載荷payload: {"user_id":1, 'username':'xx', 'exp':'超時時間'} 存放一些有效的信息,不建議有敏感的信息。
-
標准中注冊聲明(不強制):
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什么時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊
-
公共聲明
一般添加用戶的相關信息或其他業務需要的必要信息。
這部分也會進行base64url加密,構成第二部分,例如:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
-
-
簽名signature:
這部分會將上面兩部分加密后得到的字符串用
.
拼接,然后先進行hs256加密、加鹽,再進行base64url加密,組成第三部分,例如:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
-
然后將上面上部分用
.
連接,就組成了最終的token。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```
當用戶認證成功后,就會給瀏覽器返回一個token字符串,服務端不保存,下次再來請求時,會攜帶着token:
- 先進性超時驗證(ExpriredSignature)
- token合法性校驗(通過前兩部分加密對比)
優點:
- token只在前端保存,后端只負責校驗。
- 內部集成了超時時間,后端可以根據時間進行校驗是否超時。
- 由於內部存在hs256加密,所以不可以修改token,只要一修改就認證失敗。
- 不需要在服務端保存會話信息, 所以它易於應用的擴展。
缺點:
- token簽發后,不能手動使其失效。
jwt在drf中的使用
安裝
pip3 install djangorestframework-jwt
setting.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api.apps.ApiConfig',
'rest_framework',
'rest_framework_jwt' # 注冊
]
# JWT過期時間設置
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10), # 10分鍾過期
}
用戶登錄
import uuid
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning
from rest_framework import status
from rest_framework_jwt.settings import api_settings
from api import models
class LoginView(APIView):
"""
登錄接口
"""
def post(self,request,*args,**kwargs):
# 基於jwt的認證
# 1.去數據庫獲取用戶信息
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
user = models.UserInfo.objects.filter(**request.data).first()
if not user:
return Response({'code':1000,'error':'用戶名或密碼錯誤'})
payload = jwt_payload_handler(user) # 產生第二段的字典類型{'user_id':1, 'username':xxx, 'exp':當前時間+過期時間段}
token = jwt_encode_handler(payload) # 加密生成jwt的token(加密、拼接全做)
return Response({'code':1001,'data':token})
用戶認證
from rest_framework.views import APIView
from rest_framework.response import Response
# from rest_framework.throttling import AnonRateThrottle,BaseThrottle
import jwt
from rest_framework import exceptions
from rest_framework_jwt.settings import api_settings
class ArticleView(APIView):
# throttle_classes = [AnonRateThrottle,]
def get(self,request,*args,**kwargs):
# 獲取用戶提交的token,進行一步一步校驗
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_value = request.query_params.get('token')
try:
payload = jwt_decode_handler(jwt_value) # 校驗
except jwt.ExpiredSignature:
msg = '簽名已過期'
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = '認證失敗'
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenpythonticationFailed()
print(payload) # 檢驗后的第二段數據
return Response('文章列表')