一日一技:分布式系統(tǒng)的低成本權(quán)限校驗機制
經(jīng)常關(guān)注未聞Code的同學(xué)都知道,我做了一個叫做GNE[1]的開源項目,它能夠自動提取新聞類網(wǎng)頁的正文。效果遠遠好于市面上其他的開源新聞提取工具。
大家可能不知道,GNE還有一個高級版,叫做GnePro。它可以讓你輸入URL就自動提取新聞的正文,提取的字段比GNE多得多。并且已經(jīng)在8個國家13萬個網(wǎng)站上做過測試,識別準(zhǔn)確率100%。
GnePro是使用K8S搭建的爬蟲集群。背后有幾十臺服務(wù)器,通過一個網(wǎng)關(guān)做負載均衡。在設(shè)計GnePro權(quán)限機制的時候,我希望它能夠盡量簡單,盡量不依賴第三方的組件。
常規(guī)的權(quán)限校驗機制一般是這樣的,用戶登錄以后,在Cookies里面會有一個SessionId.當(dāng)用戶要查詢數(shù)據(jù)時,往后端發(fā)起請求。后端從請求中拿到這個SessionId,到Redis或者其他數(shù)據(jù)庫中,查詢到這個用戶的Session。在Session中,儲存了用戶的一些登錄信息和權(quán)限信息。再根據(jù)這個權(quán)限信息返回用戶有權(quán)限的內(nèi)容。
但這個方法需要額外引入Redis或者其他的數(shù)據(jù)庫。那么這就面臨著數(shù)據(jù)同步,并發(fā)沖突等等問題。
我的需求很簡單,只需要知道用戶的賬戶什么時候過期,用戶是什么等級就可以了。V1等級只能返回新聞?wù)?,?biāo)題,發(fā)布時間,作者,圖片。V2在V1的基礎(chǔ)上,還可以返回面包屑,SEO數(shù)據(jù),網(wǎng)頁標(biāo)簽,支持JavaScript渲染。V3還可以返回經(jīng)過清洗的網(wǎng)頁正文源代碼,支持用戶上傳HTML進行解析。因此,我不使用Session,而是使用JWT來實現(xiàn)。
這種情況下,使用JWT非常合適。JWT不需要引入第三方的組件。任何一個服務(wù)器都能獨立進行權(quán)限校驗。
例如,我定義一個數(shù)據(jù)結(jié)構(gòu),注明了用戶現(xiàn)在是什么等級,這次授權(quán)什么時候過期:
user_info = {
'level': 'v2',
'expire': '2023-12-01 00:00:00',
'name': '青南'
}
在Python中,使用PyJWT就能非常方便地生成JWT Token。首先使用pip安裝PyJWT:
python3 -m pip install pyjwt
然后3行代碼生成Token:
import jwt
user_info = {
'level': 'v1',
'expire': '2023-12-01 00:00:00',
'name': '青南'
}
password = '青南工資9999999999'
token = jwt.encode(user_info, password, algorithm='HS256')
print(token)
如下圖所示:
圖片
經(jīng)常寫爬蟲的同學(xué),可能對這個eyJh開頭的字符串很熟悉,很多網(wǎng)站的Headers里面都有長成這樣的Token。
當(dāng)一個用戶在我這里充值了會員以后,我就生成一個token發(fā)給他。當(dāng)他使用GnePro發(fā)起請求時,把這個Token放到Headers就可以了。
我的后端收到請求以后,無論當(dāng)前在哪個服務(wù)器上面,只需要執(zhí)行下面幾行代碼,就能解析出用戶權(quán)限信息:
import jwt
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6InYxIiwiZXhwaXJlIjoiMjAyMy0xMi0wMSAwMDowMDowMCIsIm5hbWUiOiJcdTk3NTJcdTUzNTcifQ.8xEkWL1pbtHKMXjrVsTtiY4JZnSMf--ufK3fiDp67SY'
password = '青南工資9999999999'
user_info = jwt.decode(token, password, algorithms=['HS256'])
print(user_info)
運行效果如下圖所示:
圖片
需要注意的是,這個JWT Token看起來這么長一串,就跟密碼一樣,但其實我們可以直接使用Jwt.io[2]這個網(wǎng)站進行解析,如下圖所示:
圖片
解析JWT Token是不需要密碼的。但是生成/修改JWT Token需要密碼。如果密碼不正確,就會生成另外一個JWT Token:
圖片
這個Token雖然跟我剛剛生成的非常像,但是由于密碼不對,我這邊進行校驗的時候就會報錯:
圖片
因此,我生成這個Token以后,我并不擔(dān)心用戶會把level改成v3。因為他沒有我的密碼,他生成的Token在我這里通不過驗證。我就能知道這個Token是否被篡改過。
整個校驗過程只需要幾行代碼,不需要任何第三方組件。完美符合少即是多的原則。
當(dāng)然JWT并不能完全替代Session。因為Session可以實時控制用戶的權(quán)限和行為。例如網(wǎng)站要做一個單點登錄,用戶在A瀏覽器登錄,就會自動在B瀏覽器登出。這個功能單獨使用JWT就做不到。
有人可能會說,你在JWT的信息里面加個SessionId不就好了嗎。后端讀到SessionId對應(yīng)的信息,就可以進行更多操作了。
但這樣做,跟直接在Cookies里面放SessionId有什么區(qū)別?JWT本來就是在輕量級的權(quán)限校驗里面使用的。它有適合自己的場景。不需要成為Session。大家也不要把JWT當(dāng)Session用。
參考資料
[1]GNE: https://github.com/GeneralNewsExtractor/GeneralNewsExtractor