JSON Web Token(JWT、JWS、JSE)


 

前言

基於session/cookie的web網站認證方式轉變為了基於OAuth2等開放授權協議的單點登錄模式(SSO),相應的基於服務器session+瀏覽器cookie的Auth手段也發生了轉變,Json Web Token出現成為了當前的熱門的Token Auth機制。

Json Web Token(JWT)

JSON Web Token(JWT)是一個非常輕巧的規范。這個規范允許我們使用JWT在兩個組織之間傳遞安全可靠的信息。

官方定義:JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties

JWT 的官方文檔:  https://jwt.io/introduction/

JWS只是JWT的一種實現,除了JWS外,JWE(JSON Web Encryption)也是JWT的一種實現:

 

什么是 JWT

一個JWT(這里應該是JWS),應該是如下形式的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

這些東西看上很凌亂,但是非常緊湊,並且是可打印的主要用於驗證簽名的真實性。

JWT 解決什么問題?

JWT的主要目的是在服務端和客戶端之間以安全的方式來轉移聲明。主要的應用場景如下所示:

1.認證 Authentication;

2.授權 Authorization // 注意這兩個單詞的區別;

3.聯合識別;

4.客戶端會話(無狀態的會話);

5.客戶端機密。

JWT 的一些名詞解釋

1.JWS:Signed JWT簽名過的jwt

2.JWE:Encrypted JWT部分payload經過加密的jwt;

目前加密payload的操作不是很普及;

3.JWK:JWT的密鑰,也就是我們常說的scret;

4.JWKset:JWT key set在非對稱加密中,需要的是密鑰對而非單獨的密鑰,在后文中會闡釋;

5.JWA:當前JWT所用到的密碼學算法;

6.nonsecure JWT:當頭部的簽名算法被設定為none的時候,該JWT是不安全的;因為簽名的部分空缺,所有人都可以修改。

0×01 JWT的組成

一個通常你看到的jwt,由以下三部分組成,它們分別是:

1.header:主要聲明了JWT的簽名算法;

2.payload:主要承載了各種聲明並傳遞明文數據;

3.signture:擁有該部分的JWT被稱為JWS,也就是簽了名的JWS;沒有該部分的JWT被稱為nonsecure JWT 也就是不安全的JWT,此時header中聲明的簽名算法為none。

三個部分用·分割。形如 xxxxx.yyyyy.zzzzz的樣式。

JWT header

{
  "typ": "JWT",
  "alg": "none",
  "jti": "4f1g23a12aa"
}

頭通常由兩部分組成:令牌的類型,即JWT,alg代表正在使用的散列算法,例如HMAC SHA256或RSA。

當然,還有兩個可選的部分,一個是jti,也就是JWT ID,代表了正在使用JWT的編號,這個編號在對應服務端應當唯一。當然,jti也可以放在payload中。

另一個是cty,也就是content type。這個比較少見,當payload為任意數據的時候,這個頭無需設置,但是當內容也帶有jwt的時候。也就是嵌套JWT的時候,這個值必須設定為jwt。這種情況比較少見。

jwt header 的加密算法

加密的方式如下:

base64UrlEncode(header)
>> eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIiwianRpIjoiNGYxZzIzYTEyYWEifQ

JWT payload

{
  "iss": "http://shaobaobaoer.cn",
  "aud": "http://shaobaobaoer.cn/webtest/jwt_auth/",
  "jti": "4f1g23a12aa",
  "iat": 1534070547,
  "nbf": 1534070607,
  "exp": 1534074147,
  "uid": 1,
  "data": {
    "uname": "shaobao",
    "uEmail": "shaobaobaoer@126.com",
    "uID": "0xA0",
    "uGroup": "guest"
  }
}

payload通常由三個部分組成,分別是 Registered Claims ; Public Claims ; Private Claims ;每個聲明,都有各自的字段。

Registered Claims

iss  【issuer】發布者的url地址

sub 【subject】該JWT所面向的用戶,用於處理特定應用,不是常用的字段

aud 【audience】接受者的url地址

exp 【expiration】 該jwt銷毀的時間;unix時間戳

nbf  【not before】 該jwt的使用時間不能早於該時間;unix時間戳

iat   【issued at】 該jwt的發布時間;unix 時間戳

jti    【JWT ID】 該jwt的唯一ID編號

Public Claims這些可以由使用JWT的那些標准化組織根據需要定義,應當參考文檔IANA JSON Web Token Registry

Private Claims這些是為在同意使用它們的各方之間共享信息而創建的自定義聲明,既不是注冊聲明也不是公開聲明。上面的payload中,沒有public claims只有private claims。

jwt payload 的加密算法

加密的方式如下:

base64UrlEncode(payload)
>> eyJpc3MiOiJodHRwOi8vc2hhb2Jhb2Jhb2VyLmNuIiwiYXVkIjoiaHR0cDovL3NoYW9iYW9iYW9lci5jbi93ZWJ0ZXN0L2p3dF9hdXRoLyIsImp0aSI6IjRmMWcyM2ExMmFhIiwiaWF0IjoxNTM0MDcwNTQ3LCJuYmYiOjE1MzQwNzA2MDcsImV4cCI6MTUzNDA3NDE0NywidWlkIjoxLCJkYXRhIjp7InVuYW1lIjoic2hhb2JhbyIsInVFbWFpbCI6InNoYW9iYW9iYW9lckAxMjYuY29tIiwidUlEIjoiMHhBMCIsInVHcm91cCI6Imd1ZXN0In19

所以,在JWT中,不應該在載荷里面加入任何敏感的數據。在上面的例子中,我們傳輸的是用戶的User ID,郵箱等。這個值實際上不是什么敏感內容,一般情況下被知道也是安全的。但是像密碼這樣的內容就不能被放在JWT中了。如果將用戶的密碼放在了JWT中,那么懷有惡意的第三方通過Base64解碼就能很快地知道你的密碼了。

當然,這也是有解決方案的,那就是加密payload。在之后會說到。

0×02 JWS 的概念

JWS 的結構

JWS ,也就是JWT Signature,其結構就是在之前nonsecure JWT的基礎上,在頭部聲明簽名算法,並在最后添加上簽名。創建簽名,是保證jwt不能被他人隨意篡改。

為了完成簽名,除了用到header信息和payload信息外,還需要算法的密鑰,也就是secret。當利用非對稱加密方法的時候,這里的secret為服務器保管的私鑰。

為了方便后文的展開,我們把JWT的密鑰或者密鑰對,統一稱為JSON Web Key,也就是JWK。

jwt signature 的簽名算法

RSASSA || ECDSA || HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
>> GQPGEpixjPZSZ7CmqXB-KIGNzNl4Y86d3XOaRsfiXmQ
>> # 上面這個是用 HMAC SHA256生成的

到目前為止,jwt的簽名算法有三種。

對稱加密HMAC【哈希消息驗證碼】:HS256/HS384/HS512

非對稱加密RSASSA【RSA簽名算法】(RS256/RS384/RS512)和ECDSA【橢圓曲線數據簽名算法】(ES256/ES384/ES512)

最后將簽名與之前的兩段內容用.連接,就可以得到經過簽名的JWT,也就是JWS。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhIn0.eyJpc3MiOiJodHRwOi8vc2hhb2Jhb2Jhb2VyLmNuIiwiYXVkIjoiaHR0cDovL3NoYW9iYW9iYW9lci5jbi93ZWJ0ZXN0L2p3dF9hdXRoLyIsImp0aSI6IjRmMWcyM2ExMmFhIiwiaWF0IjoxNTM0MDcwNTQ3LCJuYmYiOjE1MzQwNzA2MDcsImV4cCI6MTUzNDA3NDE0NywidWlkIjoxLCJkYXRhIjp7InVuYW1lIjoic2hhb2JhbyIsInVFbWFpbCI6InNoYW9iYW9iYW9lckAxMjYuY29tIiwidUlEIjoiMHhBMCIsInVHcm91cCI6Imd1ZXN0In19.GQPGEpixjPZSZ7CmqXB-KIGNzNl4Y86d3XOaRsfiXmQ 

當驗證簽名的時候,利用公鑰或者密鑰來解密Sign,和 base64UrlEncode(header) + “.” + base64UrlEncode(payload) 的內容完全一樣的時候,表示驗證通過。

JWS 的額外頭部聲明

如果對於CA有些概念的話,這些內容會比較好理解一些。為了確保服務器的密鑰對可靠有效,同時也方便第三方CA機構來簽署JWT而非本機服務器簽署JWT,對於JWS的頭部,可以有額外的聲明,以下聲明是可選的,具體取決於JWS的使用方式。如下所示:

jku: 發送JWK的地址;最好用HTTPS來傳輸

jwk: 就是之前說的JWK

kid: jwk的ID編號

x5u: 指向一組X509公共證書的URL

x5c: X509證書鏈

x5t:X509證書的SHA-1指紋

x5t#S256: X509證書的SHA-256指紋

typ: 在原本未加密的JWT的基礎上增加了 JOSE 和 JOSE+ JSON。JOSE序列化后文會說及。適用於JOSE標頭的對象與此JWT混合的情況。

crit: 字符串數組,包含聲明的名稱,用作實現定義的擴展,必須由 this->JWT的解析器處理。不常見。

多重驗證與JWS序列化

當需要多重簽名或者JOSE表頭的對象與JWS混合的時候,往往需要用到JWS的序列化。JWS的序列化結構如下所示:

{
    "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signatures": 
    [
        {
            "protected": "eyJhbGciOiJSUzI1NiJ9",
            "header": { "kid": "2010-12-29" },
            "signature":"signature1"
        },
        {
            "protected": "eyJhbGciOiJSUzI1NiJ9",
            "header": { "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d" },
            "signature":"signature2"
        },
        ...
    ]
}

結構很容易理解。首先是payload字段,這個不用多講,之后是signatures字段,這是一個數組,代表着多個簽名。每個簽名的結構如下:

protected:之前的頭部聲明,利用b64uri加密;

header:JWS的額外聲明,這段內容不會放在簽名之中,無需驗證;

signature:也就是對當前header+payload的簽名。

0×03 JWE 相關概念

JWE是一個很新的概念,總之,除了jwt的官方手冊外,很少有網站或者博客會介紹這個東西。也並非所有的庫都支持JWE。這里記錄一下自己看官方手冊后理解下來的東西。

JWS是去驗證數據的,而JWE(JSON Web Encryption)是保護數據不被第三方的人看到的。通過JWE,JWT變得更加安全。

JWE和JWS的公鑰私鑰方案不相同,JWS中,私鑰持有者加密令牌,公鑰持有者驗證令牌。而JWE中,私鑰一方應該是唯一可以解密令牌的一方。

在JWE中,公鑰持有可以將新的數據放入JWT中,但是JWS中,公鑰持有者只能驗證數據,不能引入新的數據。因此,對於公鑰/私鑰的方案而言,JWS和JWE是互補的。

  JWS JWE
producer pri_key pub_key
consumer pub_key pri_key

JWE 的構成

一個JWE,應該是如下形式的:

eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.
UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_
i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKxYxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8Otv
zlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTPcFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A.
AxY8DCtDaGlsbGljb3RoZQ.
KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.
9hH0vgRfYgPnAHOd8stkvw

如你所見JWE一共有五個部分,分別是:

The protected header,類似於JWS的頭部;

The encrypted key,用於加密密文和其他加密數據的對稱密鑰;

The initialization vector,初始IV值,有些加密方式需要額外的或者隨機的數據;

The encrypted data (cipher text),密文數據;

The authentication tag,由算法產生的附加數據,來防止密文被篡改。

JWE 密鑰加密算法

一般來說,JWE需要對密鑰進行加密,這就意味着同一個JWT中至少有兩種加密算法在起作用。但是並非將密鑰拿來就能用,我們需要對密鑰進行加密后,利用JWK密鑰管理模式來導出這些密鑰。JWK的管理模式有以下五種,分別是:

Key Encryption

Key Wrapping

Direct Key Agreement

Key Agreement with Key Wrapping

Direct Encryption

並不是所有的JWA都能夠支持這五種密鑰管理管理模式,也並非每種密鑰管理模式之間都可以相互轉換。可以參考Spomky-Labs/jose中給出的表格至於各個密鑰管理模式的細節,還請看JWT的官方手冊,解釋起來較為復雜。

JWE Header

就好像是JWS的頭部一樣。JWE的頭部也有着自己規定的額外聲明字段,如下所示:

type:一般是 jwt

alg:算法名稱,和JWS相同,該算法用於加密稍后用於加密內容的實際密鑰

enc:算法名稱,用上一步生成的密鑰加密內容的算法。

zip:加密前壓縮數據的算法。該參數可選,如果不存在則不執行壓縮,通常的值為 DEF,也就是deflate算法

jku/jkw/kid/x5u/x5c/x5t/x5t#S256/typ/cty/crit:和JWS額額外聲明一樣。

JWE 的加密過程

步驟2和步驟3,更具不同的密鑰管理模式,應該有不同的處理方式。在此只羅列一些通常情況。

之前談及,JWE一共有五個部分。現在來詳細說一下加密的過程:

1.根據頭部alg的聲明,生成一定大小的隨機數;

2.根據密鑰管理模式確定加密密鑰;

3.根據密鑰管理模式確定JWE加密密鑰,得到CEK;

4.計算初始IV,如果不需要,跳過此步驟;

5.如果ZIP頭申明了,則壓縮明文;

6.使用CEK,IV和附加認證數據,通過enc頭聲明的算法來加密內容,結果為加密數據和認證標記;

7.壓縮內容,返回token。

base64(header) + '.' +

base64(encryptedKey) + '.' + // Steps 2 and 3

base64(initializationVector) + '.' + // Step 4

base64(ciphertext) + '.' + // Step 6

base64(authenticationTag) // Step 6

多重驗證與JWE序列化

和JWS類似,JWE也定義了緊湊的序列化格式,用來完成多種形式的加密。大致格式如下所示:

{
    "protected": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
    "unprotected": { "jku":"https://server.example.com/keys.jwks" },
    "recipients":[
        {
        "header": { "alg":"RSA1_5","kid":"2011-04-29" },
        "encrypted_key":
        "UGhIOguC7Iu...cqXMR4gp_A"
        },
        {
        "header": { "alg":"A128KW","kid":"7" },
        "encrypted_key": "6KB707dM9YTIgH...9locizkDTHzBC2IlrT1oOQ"
        }
    ],
    "iv": "AxY8DCtDaGlsbGljb3RoZQ",
    "ciphertext": "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",
    "tag": "Mz-VPPyU4RlcuYv1IwIvzw"
}

結構很容易理解,如下所示:

protected:之前的頭部聲明,利用b64uri加密;

unprotected:一般放JWS的額外聲明,這段內容不會被b64加密;

iv:64加密后的iv參數;

add:額外認證數據;

ciphertext:b64加密后的加密數據;

recipients:b64加密后的認證標志-加密鏈,這是一個數組,每個數組中包含了兩個信息;

header:主要是聲明當前密鑰的算法;

encrypted_key:JWE加密密鑰。

總結:

JWT:只有Header部分(alg為none)和Payload兩個部分組成,兩個部分通過base64編碼,以一個點號拼接起來的token,缺點:無法保證token有沒有被修改。
JWS:由Header部分(alg為服務器加密算法名)、Payload部分、和Signature三個部分組成,以兩個點拼接起來的token
而JWE的計算過程相對繁瑣,不夠輕量級,因此適合與數據傳輸而非token認證,但該協議也足夠安全可靠,用簡短字符串描述了傳輸內容,兼顧數據的安全性與完整性。
refer:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM