1. 程式人生 > 實用技巧 ># codeforce 1350 F. Kuroni and the Punishment (思維+隨機化大法+shuffle洗牌)

# 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