目錄
jwt 認證規則:
介紹:
jwt: json web token
優點:
1)數據庫不需要存儲token,所以服務器的 IO 操作會減少(沒有IO寫操作)
2)客戶端存Token,服務器只存儲簽發與校驗算法,執行效率高
3)簽發與校驗算法在多個服務器上可以直接統一,所以jwt認證規則下,服務器做集群非常便捷
突破點:
1)token必須要有多個部分組成,有能反解的部分,也要有不能反解的部分 - jwt采用的都是三段式
2)token中必須包含過期時間,保證token的安全性與時效性
jwt原理:
1)jwt由 頭.載荷.簽名 三部分組成
2)每一部分數據都是一個json字典,頭和載荷采用 base64 可逆加密算法加密,簽名采用 HS256 不可逆加密
內容:
內容:
1)頭(基本信息):可逆不可逆采用的加密算法、公司名稱、項目組信息、開發者信息...
{
"company": "小女孩",
...
}
2)載荷(核心信息):用戶主鍵、用戶賬號、客戶端設備信息、過期時間...
{
'pk': 1,
...
}
3)簽名(安全信息):頭的加密結果、載荷的加密結果、服務器的安全碼(鹽)...
{
"header": "..."
...
#每一個 django 的鹽:settings.py
SECRET_KEY = '2w$ejiavxdjwbz_x9)!31^kvc1hku)=h7-bdv^f)ub!4d712*e'
}
核心算法:
簽發算法:
簽發算法:
1)頭內容寫死(可以為空{}):公司、項目組信息都是固定不變的
=> 將數據字典轉化成json字符串,再將json字符串加密成base64字符串
2)載荷的內容:用戶賬號、客戶端設備信息是由客戶端提供,用戶主鍵是客戶端提供賬號密碼校驗User表通過后才能確定,過期時間根據當前時間與配置的過期時間相結合產生
=> 將數據字典轉化成json字符串,再將json字符串加密成base64字符串
3)簽名的內容,先將頭的加密結果,載荷的加密結果作為成員,再從服務器上拿安全碼(不能讓任何客戶端知道),也可以額外包含載荷的部分(用戶信息,設備信息)
=> 將數據字典轉化成json字符串,再將json字符串不可逆加密成HS256字符串
4)將三個字符串用 . 連接產生三段式token
校驗算法:
1)從客戶端提交的請求中拿到token,用 . 分割成三段(如果不是三段,非法)
2)頭(第一段)可以不用解密
3)載荷(第二段)一定需要解密,先base64解密成json字符串,再轉換成json字典數據
i)用戶主鍵與用戶賬號查詢User表確定用戶是否存在
ii)設備信息用本次請求提交的設備信息比對,確定前后是否是同一設備,決定是否對用戶做安全提示(eg:短信郵箱提示異地登錄)(同樣的安全保障還可以為IP、登錄地點等)
iii)過期時間與當前時間比對,該token是否在有效時間內
4)簽名(第三段)采用加密碰撞校驗
i)將頭、載荷加密字符串和數據庫安全碼形成json字典,轉換成json字符串
ii)采用不可逆HS256加密形成加密字符串
iii)新的加密字符串與第三段簽名碰撞比對,一致才能確保token是合法的
5)前方算法都通過后,載荷校驗得到的User對象,就是該token代表的登錄用戶(Django項目一般都會把登錄用戶存放在request.user中)
刷新算法:
1)要在簽發token的載荷中,額外添加兩個時間信息:第一次簽發token的時間,最多往后刷新的有效時間
2)每一請求攜帶token,不僅走校驗算法驗證token是否合法,還要額外請求刷新token的接口,完成token的刷新:校驗規則與校驗算法差不多,但是要將過期時間后移(沒有超過有效時間,產生新token給客戶端,如果超過了,刷新失敗)
3)所以服務器不僅要配置過期時間,還需要配置最長刷新時間
django-rest-framework-jwt
REST框架JWT Auth 模塊
使用pip... 安裝
$ pip install djangorestframework-jwt
本質:(源碼)--ObtainJSONWebToken
def validate(self, attrs):
token = attrs['token']
payload = self._check_payload(token=token)
user = self._check_user(payload=payload)
return {
'token': token,
'user': user
}
1._check_payload 方法 (源碼解析)
2._check_user 方法
自定義配置:
# drf-jwt自定義配置
import datetime
JWT_AUTH = {
# 過期時間
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
# 是否允許刷新
'JWT_ALLOW_REFRESH': True,
# 最大刷新的過期時間
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
模塊的使用:
#子路由: urls.py
from django.conf.urls import url, include
from . import views
from .router import router
# eg: router.register('users', UserModelViewSet, basename='user')
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token, refresh_jwt_token, verify_jwt_token
urlpatterns = [
# url(r'^login/$', ObtainJSONWebToken.as_view()),
url(r'^login/$', obtain_jwt_token),
url(r'^refresh/$', refresh_jwt_token),
url(r'^verify/$', verify_jwt_token),
url(r'', include(router.urls))
]
# 發送的請求方式: post
_check_payload 方法 :
def _check_payload(self, token):
# Check payload valid (based off of JSONWebTokenAuthentication,
# may want to refactor)
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise serializers.ValidationError(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise serializers.ValidationError(msg)
return payload
_check_user 方法:
def _check_user(self, payload):
username = jwt_get_username_from_payload(payload)
if not username:
msg = _('Invalid payload.')
raise serializers.ValidationError(msg)
# Make sure user exists
try:
user = User.objects.get_by_natural_key(username)
except User.DoesNotExist:
msg = _("User doesn't exist.")
raise serializers.ValidationError(msg)
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
return user
