點擊python編程從入門到實踐,置頂 公衆號重磅 python入門資料,第一時間送達javascript
仍是牛java
讀完須要python
速讀僅需 5 分鐘web
/ python 生產實戰 安全認證的那些事兒 /算法
系統安全可能每每是被你們所忽略的,咱們的不少系統說是在互聯網上"裸奔"一點都不誇張,很容易受到攻擊,系統安全實際上是一個複雜且龐大的話題,若要詳細講來估計用幾本書的篇幅都講不完,基於此本篇及下一篇會着重講解在咱們開發系統過程當中遇到的一些安全校驗機制,但願能起到拋磚引玉的做用,望各位在開發過程當中多多思考不要只侷限於功能實現上,共勉~數據庫
在系統安全、身份驗證以及權限受權方面一般來講有各類各樣的處理方式,但大多都比較複雜。在不少框架和系統裏,涉及安全和身份驗證的工做每每都比較繁瑣,而且代碼量也巨大,基於此也出現了一些相關的協議和相關庫 咱們今天就一塊兒來了解一下相關的內容編程
1api
常見認證規範/協議瀏覽器
1.1安全
OAuth2
OAuth2 是一種協議規範,定義了幾種用來身份驗證和權限受權的處理方式。它是一種可擴展的協議規範,涵蓋了幾種複雜的使用場景。而且包含了基於第三方身份驗證的處理方法。咱們常見的"使用微信登錄"、"使用 QQ 登錄"等第三方登錄方式的底層技術就是基於 OAuth2 實現的。
1.2
OpenID Connect
OpenIDConnect 是另外一種基於 OAuth2 的協議規範。它擴展了 OAuth2 的部分功能,讓之前相對模糊的功能變得可操做性更強。常見的 Google 登錄就是基於 OpenID Connect 實現的。
1.3
OpenAPI
OpenAPI 是一套構建 API 的開放標準。FastAPI 是基於 OpenAPI 構建而成。
OpenAPI 支持如下幾種安全機制:
1.apiKey:應用指定的 key 來自於
(1) 查詢參數
(2) header 信息
(3) cookie 信息
2.http:支持標準的 http 身份驗證系統,包括:
bearer:頭信息 Authorization 的內容中帶有 Bearer 和 token 信息,繼承自 OAuth2
HTTP 基本認證
HTTP 摘要認證
3.oauth2
4.openIdConnect
FastAPI 經過引入 fastapi.security 模塊,能夠支持以上全部安全機制,而且簡化了使用方法。
2
JWT
2.1
JWT 的概念
JSON Web Token(JWT)是一個很是輕巧的規範。這個規範容許咱們使用 JWT 在用戶和服務器之間傳遞安全可靠的信息。
2.2
JWT 的組成
一個 JWT 實際上就是一個字符串,它由三部分組成:頭部、載荷與簽名。將這三段信息文本用.連接一塊兒就構成了 Jwt 字符串。就像這樣:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
頭部(Header)
JWT 的頭部承載兩部分信息:
一、聲明類型,這裏是 jwt
二、聲明加密的算法,一般直接使用 HMAC SHA256
咱們使用 base64 解析一下 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 這個子串 能夠獲得:
{ "typ": "JWT", "alg": "HS256"}
能夠看出: 在頭部指明瞭簽名算法是 HS256 算法。
2.3
載荷(Payload)
載荷就是存放有效信息的地方。這些有效信息包含三個部分
一、標準中註冊的聲明
二、公共的聲明
三、私有的聲明
標準中註冊的聲明 (建議但不強制使用) :
一、iss(Issuer): 簽發人
二、sub(Subject): 主題
三、aud(Audience): 受衆
四、exp(Expiration Time): 過時時間,這個過時時間必需要大於簽發時間
五、nbf(Not Before): 生效時間
六、iat(Issued At): 簽發時間
七、jti(JWT ID) : JWT 的惟一身份標識,主要用來做爲一次性 token,從而回避重放攻擊。
公共的聲明:
公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息。但不建議添加敏感信息,由於該部分在客戶端可解密。
私有的聲明:
私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於 base64 是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
咱們使用 base64 解析一下 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 就能夠獲得 以前定義的一個 payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
2.4
簽名(Signature)
JWT 的第三部分是一個簽證信息,這個簽證信息由三部分組成:
一、header (base64 後的)
二、payload (base64 後的)
三、secret
這個部分須要 base64 加密後的 header 和 base64 加密後的 payload 使用.鏈接組成的字符串,而後經過 header 中聲明的加密方式進行加鹽 secret 組合加密,而後就構成了 JWT 的第三部分。
Signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);Token = base64(頭部).base64(載荷).Signature
注意:secret 是保存在服務器端的,在任何場景都不該該流露出去。
2.5
使用
在請求頭 headers 中加入 Authorization,並加上 Bearer 標註
headers = {...'Authorization': 'Bearer ' + token...}
3
基於 JWT 的 Token 的認證過程
3.1
登錄認證過程
1.第一次認證:第一次登陸,用戶從瀏覽器輸入用戶名/密碼,提交後到服務器的登陸處理的 Action 層(Login Action)
2.Login Action 調用認證服務進行用戶名密碼認證,若是認證經過,LoginAction 層調用用戶信息服務獲取用戶信息(包括完整的用戶信息及對應權限信息)
3.返回用戶信息後,Login Action 從配置文件中獲取 Token 簽名生成的祕鑰信息,進行 Token 的生成
4.生成 Token 的過程當中能夠調用第三方的 JWT Lib 生成簽名後的 JWT 數據
5.完成 JWT 數據簽名後,將其設置到 COOKIE 對象中,並重定向到首頁,完成登陸過程
咱們再經過完整的圖來看一下登錄的整個認證過程:
3.2
請求認證
1.基於 Token 的認證機制會在每一次請求中都帶上完成簽名的 Token 信息,這個 Token 信息可能在 COOKIE 中,也可能在 HTTP 的 Authorization 頭中
2.客戶端(APP 客戶端或瀏覽器)經過 GET 或 POST 請求訪問資源(頁面或調用 API)
3.認證服務做爲一個 Middleware HOOK 對請求進行攔截,首先在 COOKIE 中查找 Token 信息,若是沒有找到,則在 HTTP Authorization Head 中查找
4.若是找到 Token 信息,則根據配置文件中的簽名加密祕鑰,調用 JWT Lib 對 Token 信息進行解密和解碼
5.完成解碼並驗證簽名經過後,對 Token 中的 exp、nbf、aud 等信息進行驗證
6.所有經過後,根據獲取的用戶的角色權限信息,進行對請求的資源的權限邏輯判斷
若是權限邏輯判斷經過則經過 Response 對象返回;不然則返回 HTTP 401
咱們再經過完整的圖來看一下登錄的整個請求認證過程:
3.3
基於 JWT 的 Token 認證的幾點總結:
1.一個 Token 就是一些信息的集合,是一個字符串信息
2.在 Token 中包含足夠多的信息,以便在後續請求中減小查詢數據庫的概率
3.服務端須要對 COOKIE 和 HTTP Authrorization Header 進行 Token 信息的檢查
4.基於上一點,能夠用一套 Token 認證代碼來面對瀏覽器類客戶端和非瀏覽器類客戶端
5.由於 Token 是被簽名的,因此咱們能夠認爲一個能夠解碼認證經過的 Token 是由咱們系統發放的,其中帶的信息是合法有效的
4
獲取 Token 實戰
在寫代碼以前咱們先來了解一下 OAuth2PasswordBearer 這個類的功能。OAuth2PasswordBearer 是接收 URL 做爲參數的一個類:客戶端會向該 URL 發送 username 和 password 參數,而後獲得一個 Token 值。OAuth2PasswordBearer 並不會建立相應的 URL 路徑操做,只是指明瞭客戶端用來獲取 Token 的目標 URL。
當請求到來的時候,FastAPI 會檢查請求的 Authorization 頭信息,若是沒有找到 Authorization 頭信息,或者頭信息的內容不是 Bearer Token,它會返回 401 狀態碼(UNAUTHORIZED)。咱們再從源碼上認識一下:
class OAuth2PasswordBearer(OAuth2): def __init__( self, tokenUrl: str, scheme_name: str = None, scopes: dict = None, auto_error: bool = True, ): if not scopes: scopes = {} flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]: authorization: str = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": if self.auto_error: raise HTTPException( status_code=HTTP_401_UNAUTHORIZED, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"}, ) else: return None return param
爲了完成咱們接下來的功能,須要你們進行這兩個模塊的安裝:
pip install pyjwtpip install python-multipart
簡單解釋一下:
pyjwt 是用來產生和校驗 JWT token
python-multipart 是由於 OAuth2 須要經過表單數據來發送 username 和 password 信息
在生產實踐過程當中,獲取 token 的代碼:
from datetime import datetime, timedeltafrom typing import Optional
from fastapi import Depends, FastAPIfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestFormimport jwtfrom pydantic import BaseModel
# to get a string like this run:# openssl rand -hex 32SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"ALGORITHM = "HS256"ACCESS_TOKEN_EXPIRE_MINUTES = 30
class Token(BaseModel): access_token: str token_type: str
app = FastAPI()
# oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
# 生成tokendef create_access_token(data: dict, expires_delta: Optional[timedelta] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt
# 請求接口@app.post("/token", response_model=Token)async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": "test"}, expires_delta=access_token_expires )return {"access_token": access_token, "token_type": "bearer"}
咱們運行一下代碼能夠看一下效果:
咱們對 access_token 進行一下解析來看一下每一部分的組成:
1. 子串"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"的解析結果爲:
2. 子串"eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNjE4MTExNTA4fQ"的解析結果爲:
3.子串"pwoiwQmQZbIFVvmdlmkSPXdoHrtZyoNNTRhoWAZWU9o"的解析結果爲:
5
本期總結
1.介紹了常見的 認證規範/協議
2.對 JWT 進行了深刻的研究和分析
3.在實際生產過程當中如何產生一個有效的 Token 在代碼層面進行落地
4.本篇不只可讓"守"方清楚瞭如何有效的製做一個 Token 來進行防護,另外一方面如果作逆向的"攻"方也瞭解瞭如何進行破防,下一期咱們會重點站在實踐的角度去走一個登錄請求的認證的全流程
原創不易,只願能幫助那些須要這些內容的同行或剛入行的小夥伴,你的每次 點贊、分享 都是我繼續創做下去的動力,我但願能在推廣 python 技術的道路上盡我一份力量,歡迎在評論區向我提問,我都會一一解答,記得一鍵三連支持一下哦!
加入python學習交流微信羣,請後臺回覆「入羣」
往期推薦
本文分享自微信公衆號 - python編程軍火庫(PythonCoder1024)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。