介紹一種適用於restful+json的API認證方法,這個方法是基於jwt,而且加入了一些從oauth2.0借鑑的改良。css
首先要明白,認證和鑑權是不一樣的。認證是斷定用戶的合法性,鑑權是斷定用戶的權限級別是否可執行後續操做。這裏所講的僅含認證。認證有幾種方法:html
這是http協議中所帶帶基本認證,是一種簡單爲上的認證方式。原理是在每一個請求的header中添加用戶名和密碼的字符串(格式爲「username:password」,用base64編碼)。前端
這種方式至關於將「用戶名:密碼」綁定爲一個開放式證書,這會有幾個問題:python
整體來講,這種方法的特色就是,簡單但不安全。web
將認證的結果存在客戶端的cookie中,經過檢查cookie中的身份信息來做爲認證結果。
這種方式的特色是便捷,且只須要一次認證,屢次可用;也能夠註銷登陸狀態和設置過時時間;甚至也有辦法(好比設置httpOnly)來避免XSS攻擊。算法
但它的缺點十分明顯,使用cookie那即是有狀態的服務了。數據庫
JWT協議彷佛已經應用十分普遍,JSON Web Token——一種基於token的json格式web認證方法。json
基本的原理是,第一次認證經過用戶名密碼,服務端簽發一個json格式的token。後續客戶端的請求都攜帶這個token,服務端僅須要解析這個token,來判別客戶端的身份和合法性。flask
而JWT協議僅僅規定了這個協議的格式(<a href=」https://tools.ietf.org/heml/rfc7519」>RFC7519</a>),它的序列生成方法在JWS協議中描述(https://tools.ietf.org/html/rfc7515),分爲三個部分:後端
1.3.1 header頭部:
聲明類型,這裏是jwt
聲明加密的算法 一般直接使用 HMAC SHA256
一種常見的頭部是這樣的:
再將其進行base64編碼。
1.3.2 payload載荷:
payload是放置實際有效使用信息的地方。JWT定義了幾種內容,包括:
一個常見的payload是這樣的:
事實上,payload中的內容是自由的,按照本身開發的須要加入。
Ps. 有個小問題。使用itsdangerous包的TimedJSONWebSignatureSerializer進行token序列生成的結果,exp是在頭部裏的。這裏彷佛違背了jwt的協議規則。
1.3.3 signature
存儲了序列化的secreate key和salt key。這個部分須要base64加密後的header和base64加密後的payload使用.鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret組合加密,而後就構成了jwt的第三部分。
目標場景是一個先後端分離的後端系統,用於運維工做,雖在內網使用,也有必定的保密性要求。
選擇JWT。
這裏使用python模塊itsdangerous,這個模塊能作不少編碼工做,其中一個是實現JWS的token序列。
genTokenSeq這個函數用於生成token。其中使用的是TimedJSONWebSignatureSerializer進行序列的生成,這裏secret_key密鑰、salt鹽值從配置文件中讀取,固然也能夠直接寫死在這裏。expires_in是超時時間間隔,這個間隔以秒記,能夠直接在這裏設置,我選擇將其設爲方法的形參(由於這個函數也用在瞭解決下提到的問題2)。
使用這個Serializer能夠幫咱們處理好header、signature的問題。咱們只須要用s.dumps將payload的內容寫進來。這裏我準備在每一個token中寫入三個值:用戶id、用戶角色id和當前時間(‘iat’是JWT標準註冊聲明中的一項)。
假設我所寫入的信息是
採用以上的方法所生成的token爲
eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NzM0MTQ3NCwiaWF0IjoxNDY3MzM3ODc0fQ.eyJpYXQiOjE0NjczMzc4NzQuNzE3MDYzLCJ1c2VyX2lkIjoiNDY1MDEyMjgzNDNiMTFlNmFhYTZhNDVlNjBlZDVlZDVmOTczYmEwZmNmNzgzYmI4YWRlMzRjN2I0OTJkOWU1NSIsInVzZXJfcm9sZSI6M30.23QD0OwLjdioKu5BgbaH2gHT2GoMz90n8VZcpvdyp7U
它是由「header.payload.signature」構成的。
解析須要使用到一樣的serializer,配置同樣的secret key和salt,使用loads方法來解析token。itsdangerous提供了各類異常處理類,用起來也很方便:
若是是SignatureExpired,則能夠直接返回過時;
若是是BadSignature,則表明了全部其餘簽名錯誤的狀況,因而又分爲:
以上內容寫成一個函數,用於驗證用戶token。若是實如今python flask,能夠考慮將此函數改成一個decorator修飾漆,將修飾器@到全部須要驗證token的方法前面,則代碼能夠更加優雅。
檢查和斷定的機制以下:
- 使用加密的類,再用來解密(用上以前的密鑰和鹽值),獲得結果存入data;
上述的方法能夠作到基本的JWT認證,但在實際開發過程當中還有其餘問題:
token在生成以後,是靠expire使其過時失效的。簽發以後的token,是沒法收回修改的,所以涉及token的有效期的更改是個難題,它體如今如下兩個問題:
如何解決更改token有效期的問題,網上看到不少討論,主要集中在如下內容:
這裏,筆者借鑑了第三方認證協議Oauth2.0(<a href=」https://tools.ietf.org/html/rfc6749」>RFC6749</a>),它採起了另外一種方法:refresh token,一個用於更新令牌的令牌。在用戶首次認證後,簽發兩個token:
由此能夠給兩類不一樣token設置不一樣的有效期,例如給access token僅1小時的有效時間,而refresh token則能夠是一個月。api的登出經過access token的過時來實現(前端則可直接拋棄此token實現登出),在refresh token的存續期內,訪問api時可執refresh token申請新的access token(前端可存此refresh token,access token過其實進行更新,達到自動延期的效果)。
refresh token不可再延期,過時需從新使用用戶名密碼登陸。
這種方式的理念在於,將證書分爲三種級別:
- access token 短時間證書,用於最終鑑權
經過這種方式,使證書功效和證書時效結合考慮。
ps.前面提到建立token的時候將expire_in(jwt的推薦字段,超時時間間隔)做爲函數的形參,是爲了將此函數用於生成access token和refresh token,而二者的expire_in時間是不一樣的。
咱們作了一個JWT的認證模塊:
(access token在如下代碼中爲’token’,refresh token在代碼中爲’rftoken’)
client —–用戶名密碼———–> server
client <——token、rftoken—– server
client ——請求(攜帶token)—-> server
client <—–結果—————– server
client ——請求(攜帶token)—-> server
client <—–msg:token expired— server
client -請求新token(攜帶rftoken)-> server
client <—–新token————– server
client -請求新token(攜帶rftoken)-> server
client <—-msg:rftoken expired— server
若是設計一個針對此認證的前端,須要:
存儲access token、refresh token
訪問時攜帶access token,自動檢查access token超時,超時則使用refresh token更新access token;狀態延期用戶無感知
用戶登出直接拋棄access token與refresh token
其餘參考文章: