# codeforce 1350 F. Kuroni and the Punishment (思維+隨機化大法+shuffle洗牌)
目錄
一、drf頻率原始碼分析
from rest_framework.throttling import SimpleRateThrottle
# 在頻率限制中最重要的方法就是allow_request
# 可以直接去頻率類的頂級父類裡找,告訴我們如果要寫頻率限制,就必須重寫這個方法,返回值True/False
# wait返回的數字是在前端展示的還差的時間
# 獲取rate的方法
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
# 所以在分析內建的SimpleRateThrottle頻率限制模組的時候就看這個方法········
def allow_request(self, request, view):
# 根據配置檔案的DEFAULT_THROTTLE_RATES中的scope獲取頻率配置
# scope不是寫死的,如果我們在繼承的類屬性中寫了scope=‘xxx’那麼去配置資訊找的時候就是xxx:3/m
# 我們配置的3/m
# 將這個字串分成次數和時間,分別存放到self.num_requests和self.duration中
if self.rate is None:
return True
# 呼叫get_cache_key方法,這個類中沒有寫這個方法
# 也就是我們要使用這個類就必須呼叫這個方法
# 這個方法的返回值就是我們限制頻率的約束如ip,username,如果返回None就不限制
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
# 把訪問資訊新增到快取當中
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# 根據時間判斷是否超出限制
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
# 列表長度如果超出我們限定的就返回錯誤
return self.throttle_failure()
# 如果沒超出就返回True並把列表儲存到快取中
return self.throttle_success()
二、自動生成介面檔案
REST framework可以自動幫助我們生成介面檔案。
介面檔案以網頁的方式呈現。
自動介面檔案能生成的是繼承自APIView
及其子類的檢視。
1 安裝依賴
REST framewrok生成介面檔案需要coreapi
庫的支援。
pip install coreapi
2 設定介面檔案訪問路徑
在總路由中新增介面檔案路徑。
檔案路由對應的檢視配置為rest_framework.documentation.include_docs_urls
,
引數title
為介面檔案網站的標題。
from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
path('docs/', include_docs_urls(title='站點頁面標題'))
]
3 檔案描述說明的定義位置
1) 單一方法的檢視,可直接使用類檢視的檔案字串,如
class BookListView(generics.ListAPIView):
"""
返回所有圖書資訊.
"""
2)包含多個方法的檢視,在類檢視的檔案字串中,分開方法定義,如
class BookListCreateView(generics.ListCreateAPIView):
"""
get:
返回所有圖書資訊.
post:
新建圖書.
"""
3)對於檢視集ViewSet,仍在類檢視的檔案字串中封開定義,但是應使用action名稱區分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
list:
返回圖書列表資料
retrieve:
返回圖書詳情資料
latest:
返回最新的圖書資料
read:
修改圖書的閱讀量
"""
4 訪問介面檔案網頁
瀏覽器訪問 127.0.0.1:8000/docs/,即可看到自動生成的介面檔案。
三、JWT
1 JWT基本原理
參考:http://liuqingzheng.top/python/Django-rest-framework框架/9-drf-JWT認證/
JWT = Json Web Token,本質上就是token
分散式和叢集
分散式是指通過網路連線的多個元件,通過交換資訊協作而形成的系統。而叢集,是指同一種元件的多個例項,形成的邏輯上的整體。
簡單來說叢集就是,專案所用的所有功能,前後端,資料庫,快取都放在一個機器上,然後多放幾個這樣的機器,上面的專案都是一樣的
分散式就是我把一個專案用的東西分別放在不同的機器上,a機器裝資料庫,b機器放後端程式碼等等
所以叢集相對於分散式有負載均衡的效果,一臺機子宕機了,這個站點也能繼續被訪問,而分散式一處死了,就完全沒法繼續了
jwt的應用場景:當我們用分散式的專案的時候,如果是統一的用一個資料庫,那沒問題,如果是各個機器用自己的資料庫,那如果我在a機器上註冊賬號,登入了,下次如果訪問的是b伺服器就又得重新登入,因為我的登入狀態不在這個機子上。
jwt認證可以讓客戶端傳送的資料有服務端的特性,檢驗這種特性來判斷是否是這個服務端的資料,再根據特定的規則解碼得到我們需要的資料。
jwt資料構成
JWT就是一段字串,由三段資訊構成的,將這三段資訊文字用.
連結一起就構成了Jwt字串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).
1.1 header
jwt的頭部承載兩部分資訊:
- 宣告型別,這裡是jwt
- 宣告加密的演演算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然後將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
1.2 payload
載荷就是存放有效資訊的地方。這個名字像是特指飛機上承載的貨品,這些有效資訊包含三個部分
- 標準中註冊的宣告
- 公共的宣告
- 私有的宣告
標準中註冊的宣告 (建議但不強制使用) :
- 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
1.3 signature
JWT的第三部分是一個簽證資訊,這個簽證資訊由三部分組成:
- header (base64後的)
- payload (base64後的)
- secret
這個部分需要base64加密後的header和base64加密後的payload使用.
連線組成的字串,然後通過header中宣告的加密方式進行加鹽secret
組合加密,然後就構成了jwt的第三部分。
// javascript
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擴充套件來完成。
1.4 認證演演算法:簽發與校驗
"""
1)jwt分三段式:頭.體.簽名 (head.payload.sgin)
2)頭和體是可逆加密,讓伺服器可以反解出user物件;簽名是不可逆加密,保證整個token的安全性的
3)頭體簽名三部分,都是採用json格式的字串,進行加密,可逆加密一般採用base64演演算法,不可逆加密一般採用hash(md5)演演算法
4)頭中的內容是基本資訊:公司資訊、專案組資訊、token採用的加密方式資訊
{
"company": "公司資訊",
...
}
5)體中的內容是關鍵資訊:使用者主鍵、使用者名稱、簽發時客戶端資訊(裝置號、地址)、過期時間
{
"user_id": 1,
...
}
6)簽名中的內容時安全資訊:頭的加密結果 + 體的加密結果 + 伺服器不對外公開的安全碼 進行md5加密
{
"head": "頭的加密字串",
"payload": "體的加密字串",
"secret_key": "安全碼"
}
"""
簽發:根據登入請求提交來的 賬號 + 密碼 + 裝置資訊 簽發 token
"""
1)用基本資訊儲存json字典,採用base64演演算法加密得到 頭字串
2)用關鍵資訊儲存json字典,採用base64演演算法加密得到 體字串
3)用頭、體加密字串再加安全碼資訊儲存json字典,採用hash md5演演算法加密得到 簽名字串
賬號密碼就能根據User表得到user物件,形成的三段字串用 . 拼接成token返回給前臺
"""
校驗:根據客戶端帶token的請求 反解出 user 物件
"""
1)將token按 . 拆分為三段字串,第一段 頭加密字串 一般不需要做任何處理
2)第二段 體加密字串,要反解出使用者主鍵,通過主鍵從User表中就能得到登入使用者,過期時間和裝置資訊都是安全資訊,確保token沒過期,且時同一裝置來的
3)再用 第一段 + 第二段 + 伺服器安全碼 不可逆md5加密,與第三段 簽名字串 進行碰撞校驗,通過後才能代表第二段校驗得到的user物件就是合法的登入使用者
"""
2 jwt的基本使用
安裝:pip install djangorestframework-jwt
# 路由配置
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
# settings註冊app
# 在postman傳送JSON格式的註冊的使用者賬號和密碼
{
"username": "hz",
"password":"hz123456"
}
# 返回的jwt資料
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Imh6IiwiZXhwIjoxNTk0NzE1MTEyLCJlbWFpbCI6IiJ9.3Qos0Ua0hZfK2Nn3P0jR6yxNSrBeloIVplbUBZILUpM"
}
# 在我們要訪問設定的認證配置的url時,要注意必須在請求頭中設定
Authorization : JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Imh6IiwiZXhwIjoxNTk0NzE1MTEyLCJlbWFpbCI6IiJ9.3Qos0Ua0hZfK2Nn3P0jR6yxNSrBeloIVplbUBZILUpM"
# 注意這裡是JWT空格+token資訊,這是jwt認證寫死的,必須按照這個規範來
3 自定製auth認證類
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
# 重寫父類的authenticate即可
class MyToekn(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_value = get_authorization_header(request).split()[1]
# jwt_value = str(request.META.get('HTTP_AUTHORIZATION'))
if not jwt_value:
raise AuthenticationFailed('Authorization 欄位是必須的')
try:
payload = jwt_decode_handler(jwt_value)
except Exception:
raise AuthenticationFailed('認證失敗')
user = self.authenticate_credentials(payload)
return user, jwt_value