理解JWT(JSON Web Token)認證及python實踐

最近想作個小程序,須要用到受權認證流程。之前項目都是用的 OAuth2 認證,可是Sanic 使用OAuth2 不太方便,就想試一下 JWT 的認證方式。
這一篇主要內容是 JWT 的認證原理,以及python 使用 jwt 認識的實踐。html

幾種經常使用的認證機制

HTTP Basic Auth

HTTP Basic Auth 在HTTP中,基本認證是一種用來容許Web瀏覽器或其餘客戶端程序在請求時提供用戶名和口令形式的身份憑證的一種登陸驗證方式,一般用戶名和明碼會經過HTTP頭傳遞。前端

在發送以前是以用戶名追加一個冒號而後串接上口令,並將得出的結果字符串再用Base64算法編碼。例如,提供的用戶名是Aladdin、口令是open sesame,則拼接後的結果就是Aladdin:open sesame,而後再將其用Base64編碼,獲得QWxhZGRpbjpvcGVuIHNlc2FtZQ==。最終將Base64編碼的字符串發送出去,由接收者解碼獲得一個由冒號分隔的用戶名和口令的字符串。python

優勢
基本認證的一個優勢是基本上全部流行的網頁瀏覽器都支持基本認證。git

缺點
因爲用戶名和密碼都是Base64編碼的,而Base64編碼是可逆的,因此用戶名和密碼能夠認爲是明文。因此只有在客戶端和服務器主機之間的鏈接是安全可信的前提下才可使用。github

接下來咱們看一個更加安全也適用範圍更大的認證方式 OAuthweb

OAuth

OAuth 是一個關於受權(authorization)的開放網絡標準。容許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。如今的版本是2.0版。算法

嚴格來講,OAuth2不是一個標準協議,而是一個安全的受權框架。它詳細描述了系統中不一樣角色、用戶、服務前端應用(好比API),以及客戶端(好比網站或移動App)之間怎麼實現相互認證。數據庫

名詞定義

  • Third-party application: 第三方應用程序,又稱"客戶端"(client)
  • HTTP service:HTTP服務提供商
  • Resource Owner:資源全部者,一般稱"用戶"(user)。
  • User Agent:用戶代理,好比瀏覽器。
  • Authorization server:認證服務器,即服務提供商專門用來處理認證的服務器。
  • Resource server:資源服務器,即服務提供商存放用戶生成的資源的服務器。它與認證服務器,能夠是同一臺服務器,也能夠是不一樣的服務器。

OAuth 2.0 運行流程如圖:json

OAuth 2.0 運行流程
OAuth 2.0 運行流程

(A)用戶打開客戶端之後,客戶端要求用戶給予受權。
(B)用戶贊成給予客戶端受權。
(C)客戶端使用上一步得到的受權,向認證服務器申請令牌。
(D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放令牌。
(E)客戶端使用令牌,向資源服務器申請獲取資源。
(F)資源服務器確認令牌無誤,贊成向客戶端開放資源。小程序

優勢
快速開發
實施代碼量小
維護工做減小
若是設計的API要被不一樣的App使用,而且每一個App使用的方式也不同,使用OAuth2是個不錯的選擇。

缺點
OAuth2是一個安全框架,描述了在各類不一樣場景下,多個應用之間的受權問題。有海量的資料須要學習,要徹底理解須要花費大量時間。
OAuth2不是一個嚴格的標準協議,所以在實施過程當中更容易出錯。

瞭解了以上兩種方式後,如今終於到了本篇的重點,JWT 認證。

JWT 認證

Json web token (JWT), 根據官網的定義,是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。

JWT 特色

  • 體積小,於是傳輸速度快
  • 傳輸方式多樣,能夠經過URL/POST參數/HTTP頭部等方式傳輸
  • 嚴格的結構化。它自身(在 payload 中)就包含了全部與用戶相關的驗證消息,如用戶可訪問路由、訪問有效期等信息,服務器無需再去鏈接數據庫驗證信息的有效性,而且 payload 支持爲你的應用而定製化。
  • 支持跨域驗證,能夠應用於單點登陸。

JWT原理

JWT是Auth0提出的經過對JSON進行加密簽名來實現受權驗證的方案,編碼以後的JWT看起來是這樣的一串字符:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ複製代碼

. 分爲三段,經過解碼能夠獲得:

1. 頭部(Header)

// 包括類別(typ)、加密算法(alg);
{
  "alg": "HS256",
  "typ": "JWT"
}複製代碼

jwt的頭部包含兩部分信息:

  • 聲明類型,這裏是jwt
  • 聲明加密的算法 一般直接使用 HMAC SHA256

而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9複製代碼

2. 載荷(payload)

載荷就是存放有效信息的地方。這些有效信息包含三個部分:

  • 標準中註冊聲明
  • 公共的聲名
  • 私有的聲明

公共的聲明 :
公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密。

私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。

下面是一個例子:

// 包括須要傳遞的用戶信息;
{ "iss": "Online JWT Builder", 
  "iat": 1416797419, 
  "exp": 1448333419, 
  "aud": "www.gusibi.com", 
  "sub": "uid", 
  "nickname": "goodspeed", 
  "username": "goodspeed", 
  "scopes": [ "admin", "user" ] 
}複製代碼
  • iss: 該JWT的簽發者,是否使用是可選的;
  • sub: 該JWT所面向的用戶,是否使用是可選的;
  • aud: 接收該JWT的一方,是否使用是可選的;
  • exp(expires): 何時過時,這裏是一個Unix時間戳,是否使用是可選的;
  • iat(issued at): 在何時簽發的(UNIX時間),是否使用是可選的;

其餘還有:

  • nbf (Not Before):若是當前時間在nbf裏的時間以前,則Token不被接受;通常都會留一些餘地,好比幾分鐘;,是否使用是可選的;
  • jti: jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。

將上面的JSON對象進行base64編碼能夠獲得下面的字符串。這個字符串咱們將它稱做JWT的Payload(載荷)。

eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0複製代碼

信息會暴露:因爲這裏用的是可逆的base64 編碼,因此第二部分的數據其實是明文的。咱們應該避免在這裏存放不能公開的隱私信息。

3. 簽名(signature)

// 根據alg算法與私有祕鑰進行加密獲得的簽名字串;
// 這一段是最重要的敏感信息,只能在服務端解密;
HMACSHA256(  
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    SECREATE_KEY
)複製代碼

jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:

  • header (base64後的)
  • payload (base64後的)
  • secret

將上面的兩個編碼後的字符串都用句號.鏈接在一塊兒(頭部在前),就造成了:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9複製代碼

最後,咱們將上面拼接完的字符串用HS256算法進行加密。在加密的時候,咱們還須要提供一個密鑰(secret)。若是咱們用 secret 做爲密鑰的話,那麼就能夠獲得咱們加密後的內容:

pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o複製代碼

將這三部分用.鏈接成一個完整的字符串,構成了最終的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0.pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o複製代碼

簽名的目的:簽名其實是對頭部以及載荷內容進行簽名。因此,若是有人對頭部以及載荷的內容解碼以後進行修改,再進行編碼的話,那麼新的頭部和載荷的簽名和以前的簽名就將是不同的。並且,若是不知道服務器加密的時候用的密鑰的話,得出來的簽名也必定會是不同的。
這樣就能保證token不會被篡改。

token 生成好以後,接下來就能夠用token來和服務器進行通信了。

下圖是client 使用 JWT 與server 交互過程:

client 使用 JWT 與server 交互過程
client 使用 JWT 與server 交互過程

這裏在第三步咱們獲得 JWT 以後,須要將JWT存放在 client,以後的每次須要認證的請求都要把JWT發送過來。(請求時能夠放到 header 的 Authorization )

JWT 使用場景

JWT的主要優點在於使用無狀態、可擴展的方式處理應用中的用戶會話。服務端能夠經過內嵌的聲明信息,很容易地獲取用戶的會話信息,而不須要去訪問用戶或會話的數據庫。在一個分佈式的面向服務的框架中,這一點很是有用。

可是,若是系統中須要使用黑名單實現長期有效的token刷新機制,這種無狀態的優點就不明顯了。

優勢
快速開發
不須要cookie
JSON在移動端的普遍應用
不依賴於社交登陸
相對簡單的概念理解

缺點
Token有長度限制
Token不能撤銷
須要token有失效時間限制(exp)

python 使用JWT實踐

我基本是使用 python 做爲服務端語言,咱們可使用 pyjwt:https://github.com/jpadilla/pyjwt/

使用比較方便,下邊是我在應用中使用的例子:

import jwt
import time

# 使用 sanic 做爲restful api 框架 
def create_token(request):
    grant_type = request.json.get('grant_type')
    username = request.json['username']
    password = request.json['password']
    if grant_type == 'password':
        account = verify_password(username, password)
    elif grant_type == 'wxapp':
        account = verify_wxapp(username, password)
    if not account:
        return {}
    payload = {
        "iss": "gusibi.com",
         "iat": int(time.time()),
         "exp": int(time.time()) + 86400 * 7,
         "aud": "www.gusibi.com",
         "sub": account['_id'],
         "username": account['username'],
         "scopes": ['open']
    }
    token = jwt.encode(payload, 'secret', algorithm='HS256')
    return True, {'access_token': token, 'account_id': account['_id']}


def verify_bearer_token(token):
    # 若是在生成token的時候使用了aud參數,那麼校驗的時候也須要添加此參數
    payload = jwt.decode(token, 'secret', audience='www.gusibi.com', algorithms=['HS256'])
    if payload:
        return True, token
    return False, token複製代碼

這裏,咱們可使用 jwt 直接生成 token,不用手動base64加密和拼接。

詳細代碼能夠參考 gusibi/Metis: 一個測試類小程序(包含先後端代碼)

這個項目中,api 使用 python sanic,文檔使用 swagger-py-codegen 生成,提供 swagger ui。

如今可使用 swagger ui 來測試jwt。

swagger ui 界面
swagger ui 界面

總結

這一篇主要介紹了 jwt 的原理、驗證步驟,最後是使用 pyjwt 包演示 生成token以及校驗token的方法。

以上提到的包能夠在公號回覆關鍵字獲取地址

預告,下一篇是介紹小程序中使用 JWT 的認證流程及實現。

參考連接


最後,感謝女友支持。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注
歡迎關注
請我喝芬達
請我喝芬達
相關文章
相關標籤/搜索