文章分享

開放、平等、協(xié)作、快速、分享

當(dāng)前位置:首頁>文章分享

JSON Web Tokens (JWT) 與 Sessions

摘錄:HCTech 無錫和控電子   時(shí)間:2020-08-07   訪問量:3596

什么是 JWT?

本質(zhì)上它是一段簽名的 JSON 格式的數(shù)據(jù)。由于它是帶有簽名的,因此接收者便可以驗(yàn)證它的真實(shí)性。同時(shí)由于它是 JSON 格式的因此它的體積也很小。如果你想了解有關(guān)它的正式定義,可以在 RFC 7519 中找到。

這篇文章發(fā)布于黑客新聞上。在這里也可以看一下關(guān)于這篇文章的案例分析,它主要包含了文章內(nèi)容的公開分析、SEO 影響、性能影響以及更多其他的內(nèi)容。

數(shù)據(jù)簽名已經(jīng)不是什么新事物了 - 令人值得興奮的是如何在不依靠 sessions 的情況下使用 JWT 創(chuàng)建真正的 RESTful 服務(wù),目前這個(gè)想法已經(jīng)被事實(shí)證明有一段時(shí)間了。下面是介紹它在現(xiàn)實(shí)中具體實(shí)現(xiàn)的工作原理 - 首先在這里我來做一個(gè)類比:

想象一下你剛從國(guó)外度完假回來,你在邊境上說 - 你可以讓我通過,我是這里的公民。這樣的回答很好也沒有問題,但是你要如何去支持你的說法呢?最有可能的方案是你攜帶了護(hù)照來證明你的身份。這里我們假設(shè)邊境工作人員也都被要求去核實(shí)護(hù)照是真正由你的國(guó)家的護(hù)照辦簽發(fā)的。那么護(hù)照就會(huì)被核實(shí),這樣他們也才會(huì)放你回國(guó)。

現(xiàn)在,讓我們從 JWT 的角度看一下這個(gè)故事,它們各自又都扮演著什么樣的角色:

看啊!沒有 session!

簡(jiǎn)單來說,JWT 非常的酷,因?yàn)槟悴挥迷贋榱髓b別用戶而在你的服務(wù)器上去保留你的 session 數(shù)據(jù)。這個(gè)工作流將會(huì)變得像下面這樣:

讓我們考慮一下這樣做的結(jié)果。

沒有會(huì)話存儲(chǔ)

沒有 sessions 意味著你沒有會(huì)話存儲(chǔ)。但除非您的應(yīng)用程序需要橫向擴(kuò)展,否則這也不太重要,如果你的應(yīng)用程序是運(yùn)行在多個(gè)服務(wù)器上的,那么共享 session 數(shù)據(jù)將會(huì)成為一個(gè)負(fù)擔(dān)。你需要一個(gè)專門的服務(wù)器來只存儲(chǔ)會(huì)話數(shù)據(jù)或是共享磁盤空間或是在負(fù)載均衡上粘滯會(huì)話。當(dāng)你不使用 sessions 時(shí)上面的這些也就自然不再需要了。

沒有對(duì) sessions 的垃圾收集

通常來講 sessions 需要留意過期和垃圾收集的情況。JWT 可以在用戶數(shù)據(jù)中包含自己的過期日期。因此安全層在檢驗(yàn) JWT 的授權(quán)時(shí)可以同時(shí)核對(duì)它的過期時(shí)間來拒絕訪問。

真正的 RESTful 服務(wù)

只有在無 sessions 的情況下你可以創(chuàng)建真正的 RESTful 服務(wù),因?yàn)樗徽J(rèn)為是無狀態(tài)的。 JWT 很小所以它可以在每一個(gè)請(qǐng)求中被一起發(fā)出去,就像一個(gè) session cookie一樣。然而與 session cookie 不同的是,它并不指向服務(wù)器上的任何存儲(chǔ)數(shù)據(jù), JWT 本身包含了這些數(shù)據(jù)。

真實(shí)的 JWT 到底是什么樣的?

在我們更深入討論之前,有一件事需要了解。JWT 自身并不是一個(gè)東西。它是 JSON 網(wǎng)絡(luò)簽名(JWS)或 JSON 網(wǎng)絡(luò)加密 (JWE)中的一種類型。它的定義如下:

一個(gè) JWT 的聲明內(nèi)容會(huì)被編碼為一個(gè) JSON 對(duì)象,它被作為 JSON 網(wǎng)絡(luò)簽名結(jié)構(gòu)的有效載荷或是作為 JSON 網(wǎng)絡(luò)加密結(jié)構(gòu)的明文信息。

前者給我們的只是一個(gè)簽名并且它包含的數(shù)據(jù)(或是平時(shí)所稱呼的 "claims" 的命名)是對(duì)任何人都可讀的。后者則提供了加密的內(nèi)容,所以只有擁有密鑰的人可以解密它。JWS 在實(shí)現(xiàn)上更加容易并且基本用法上是不需要加密的 - 畢竟如果你在客戶端上有密鑰的話,你還不如把所有的東西不加密的好。因此 JWS 在大多數(shù)情況下都是適用的,也因此在之后我將主要關(guān)注 JWS。

那么 JWT/JWS 是由什么構(gòu)成的?

我將在之后具體解釋這些細(xì)節(jié)?,F(xiàn)在讓我們先來分析下基礎(chǔ)要素。

上述所提到的每一部分(頭部,負(fù)載和簽名)是基于 base64url 編碼的,然后他們用 '.' 作為分隔符粘連起來組成 JWT。 下面是這個(gè)實(shí)現(xiàn)方式可能看上去的樣子:

var header = {  
        // The signing algorithm.
        "alg": "HS256",        // The type (typ) property says it's "JWT",
        // because with JWS you can sign any type of data.
        "typ": "JWT"
    },    // Base64 representation of the header object.
    headerB64 = btoa(JSON.stringify(header)),    // The payload here is our JWT claims.
    payload = {        "name": "John Doe",        "admin": true
    },    // Base64 representation of the payload object.
    payloadB64 = btoa(JSON.stringify(payload)),    // The signature is calculated on the base64 representation
    // of the header and the payload.
    signature = signatureCreatingFunction(headerB64 + '.' + payloadB64),    // Base64 representation of the signature.
    signatureB64 = btoa(signature),    // Finally, the whole JWS - all base64 parts glued together with a '.'
    jwt = headerB64 + '.' + payloadB64 + '.' + signatureB64;

由此得到的 JWS 結(jié)果看上去整潔而優(yōu)雅,有點(diǎn)像這樣:

`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.OLvs36KmqB9cmsUrMpUutfhV52_iSz4bQMYJjkI_TLQ`

你也可以試著在 jwt.io 這個(gè)網(wǎng)站上來創(chuàng)建令牌試試。

有一點(diǎn)相當(dāng)重要,那就是簽名是依據(jù)頭部和負(fù)載計(jì)算出來的。因此頭部和負(fù)載的授權(quán)也很容易同樣被檢驗(yàn):

[headerB64, payloadB64, signatureB64] = jwt.split('.');if (atob(signatureB64) === signatureCreatingFunction(headerB64 + '.' + payloadB64) {  
    // good} else
    // no good}

JWT 頭部中可以存放什么?

事實(shí)上,JWT 頭部被稱為 JOSE 頭部。JOSE 表示的是 JSON 對(duì)象的簽名和加密。也正如你期望的那樣,JWS 和 JWE 都是這樣的一個(gè)頭部,然而它們各自之間存在著一套稍微不同的注冊(cè)參數(shù)。下面是在 JWS 中使用的頭部注冊(cè)參數(shù)列表。所有的參數(shù)除了第一個(gè)參數(shù)(alg)以外,其他參數(shù)都是可選的:

前兩個(gè)參數(shù)是最常用的,所以典型的頭部看起來有點(diǎn)類似下面這樣:

{    "alg": "HS256",    "typ": "JWT"}

上面列出的第三個(gè)參數(shù) kid 是基于安全原因使用的。cty 參數(shù)在另一方面應(yīng)該只被用于處理嵌套的 JWT。剩下的參數(shù)你可以在規(guī)范文檔中閱讀了解,我認(rèn)為它們不適合在這篇文章中被提及。

alg (算法)

alg 參數(shù)的值可以是 JSON 網(wǎng)絡(luò)算法(JWA)中的任意指定值 - 這是我所知道的另一個(gè)規(guī)范。下面是 JWS 的注冊(cè)列表:

請(qǐng)注意最后一個(gè)值 none,從安全性的角度來看這是最有趣的。這是已知的被用來進(jìn)行降級(jí)防御攻擊的方法。它是如何工作的呢?想象一個(gè)客戶端生成的帶有一些聲明的 JWT 。它在頭部指定 none 值的簽名算法并進(jìn)行發(fā)送驗(yàn)證。如果攻擊者比較單純,那么它會(huì)使 alg 參數(shù)為真來確保被授權(quán)通過,然而實(shí)際上則是不會(huì)被允許的。

底線是,你的應(yīng)用的安全層應(yīng)該總是對(duì)頭部的 alg 參數(shù)進(jìn)行校驗(yàn)。那里就是 kid 參數(shù)用的上的地方。

typ (類型)

這一個(gè)參數(shù)非常簡(jiǎn)單。如果它是已知的,那么它就是 JWT,因?yàn)閼?yīng)用不會(huì)去索取其他的值,如果這個(gè)參數(shù)沒有值就會(huì)被忽視掉。因此它是可選的。如果需要被指定值,它應(yīng)該按大寫字母拼寫 - JWT 。

在某些情況下,當(dāng)應(yīng)用程序接受到?jīng)]有 JWT 類型的請(qǐng)求卻又包含了 JWT 時(shí),去重新指定它是很重要的,因?yàn)檫@樣應(yīng)用程序才不會(huì)崩潰。

kid (密鑰 id)

如果你的應(yīng)用程序中的安全層只使用了一個(gè)算法來簽名 JWTs,你不用太擔(dān)心 alg 參數(shù),因?yàn)槟銜?huì)總是使用相同的密鑰和算法來校驗(yàn)令牌的完整性。但是,如果你的應(yīng)用程序使用了一堆不同的算法和密鑰,你就需要能夠分辨出是由誰簽署的令牌。

正如我們之前看到的,單獨(dú)依靠 alg 參數(shù)可能會(huì)導(dǎo)致一些...不便。然而,如果你的應(yīng)用維護(hù)了一個(gè)密鑰/算法的列表,并且每一對(duì)都有一個(gè)名稱(id),你可以添加這個(gè)密鑰 id 到頭部,這樣在之后驗(yàn)證 JWT 時(shí)你會(huì)有更多的信心去選擇算法。這就是頭部參數(shù) kid - 你的應(yīng)用中用來簽名令牌所使用的密鑰 id 。這個(gè) id 是由你來任意指定的。最重要的是 - 這是你給的 id ,所以你可以驗(yàn)證。

cty (內(nèi)容類型)

這里把規(guī)范介紹的很清楚,所以這里我就只是引用了:

在通常情況下,在不使用嵌套簽名或是加密操作時(shí),是不推薦使用這個(gè)頭部參數(shù)的。而在使用嵌套簽名或加密時(shí),這個(gè)頭部參數(shù)必須存在;在這種情況下,它的值必須是 "JWT",來表明這是一個(gè)在 JWT 中嵌套的 JWT。雖然媒體類型名字對(duì)大小寫并不敏感,但這里為了與現(xiàn)有遺留實(shí)現(xiàn)兼容還是推薦始終用 "JWT" 大寫字母來拼寫。

在 JWT 聲明中可以有什么?

"claims" 這個(gè)名稱是否讓你感到困惑?在最初它也確實(shí)讓我很困惑。我相信你需要重復(fù)讀幾次來嘗試適應(yīng)它。簡(jiǎn)而言之,claims 是 JWT 的主要內(nèi)容 - 是我們十分關(guān)心的簽名的數(shù)據(jù)。它被叫做 "claims" 是因?yàn)橥ǔK褪锹暶鬟@個(gè)意思 - 客戶端聲明了用戶名,用戶角色或者其他什么的來讓它可以獲得對(duì)資源的訪問。

還記得我在最開始提到的那個(gè)可愛的故事嗎?你的公民資格就是你的聲明而你的護(hù)照則就是 - JWT

你可以在聲明中放置任何你想要的參數(shù),這兒有一個(gè)注冊(cè)表應(yīng)當(dāng)被視為公認(rèn)的參考實(shí)現(xiàn)方法。請(qǐng)注意這里的每一個(gè)參數(shù)都是可選的并且大多數(shù)是應(yīng)用程序特定的,下面就是這個(gè)列表:

值得注意的是,除了最后三個(gè)(issuer ,audience 和 JWT ID)參數(shù)通常是在更復(fù)雜的情況下(例如包含多個(gè)發(fā)行者時(shí))才被使用。下面讓我們來討論一下它們吧。

exp (過期時(shí)間)

exp 是時(shí)間戳值表示著在什么時(shí)候令牌會(huì)失效。規(guī)范上要求"當(dāng)前日期/時(shí)間"必須在指定的 exp 值之前,從而保證令牌可以得到處理。這里也表明了存在一些余地(幾分鐘)來應(yīng)對(duì)時(shí)間差。

nbf (有效起始時(shí)間)

nbf 是時(shí)間戳值表示著在什么時(shí)候令牌開始生效。規(guī)范上要求"當(dāng)前日期/時(shí)間"必須與指定的 nbf 值相等或在其之后,從而保證令牌可以得到處理。這里也表明了存在一些余地(幾分鐘)來應(yīng)對(duì)時(shí)間差。

iat (發(fā)行時(shí)間)

iat 是時(shí)間戳值表示什么時(shí)候令牌被發(fā)行。

sub (主題)

sub 在規(guī)范上被要求"是JWT 中的聲明中通常用于陳述主題的值"。這里主題必須是內(nèi)容中唯一的發(fā)行者或全局上的唯一值。sub 聲明可以用來鑒別用戶,例如 JIRA 文檔上那樣。

iss (發(fā)行者)

iss 是被用來確認(rèn)令牌的發(fā)行者的字符串值。如果值中包含 : 那么它就是一個(gè) URI。如果有很多的發(fā)行者而在一個(gè)安全層中應(yīng)用程序需要去識(shí)別發(fā)行人時(shí),它將會(huì)是有用的。例如 Salesforce 要求了去使用 OAuth client_id 來作為 iss 的值。

aud (受眾)

aud 是被用來確認(rèn)令牌的可能接受者的字符串值或數(shù)組。如果值中包含 : 那么它就是一個(gè) URI。 通常使用 URI 資源的聲明是有效的。例如,在 OAuth 中,接受者是授權(quán)服務(wù)器。應(yīng)用程序處理令牌時(shí),在針對(duì)不同的接受者的情況下,必須驗(yàn)證接受者是否是正確的或者拒絕令牌。

jti (JWT id)

令牌的唯一標(biāo)識(shí)符。每個(gè)發(fā)布的令牌的 jti 必須是唯一的,即使有很多發(fā)行人也是一樣。jti 聲明可以用于一次性的不能重放的令牌。

如何在我的應(yīng)用中使用 JWT ?

在最常見的場(chǎng)景中,客戶端的瀏覽器將在認(rèn)證服務(wù)中認(rèn)證并接受返回的 JWT。然后客戶端用某種方式(如內(nèi)存,localStorage)存儲(chǔ)這個(gè)令牌并與受保護(hù)的資源一起發(fā)送返回。通常令牌發(fā)送時(shí)是作為 cookie 或是 HTTP 請(qǐng)求中 Authorization 頭部。

GET /api/secured-resource HTTP/1.1  Host: example.com  Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.OLvs36KmqB9cmsUrMpUutfhV52_iSz4bQMYJjkI_TLQ

首選頭部方法是出于安全的原因 - cookies 會(huì)很容易受 CSRF (跨站請(qǐng)求偽造)的影響,除非 CSRF 令牌是使用過的。

其次,cookies 只能發(fā)送返回到被發(fā)出的相同的域下(或者最多二級(jí)域下)。如果身份驗(yàn)證服務(wù)駐留在不同的域下,那么 cookies 得需要更強(qiáng)烈的創(chuàng)造性才行。

如何通過 JWT 登出?

因?yàn)闆]有 session 數(shù)據(jù)存儲(chǔ)在服務(wù)端了,所以不能再通過破壞 session 來注銷了。因此登出成為了客戶端的職責(zé) - 一旦客戶丟失了令牌不能再被授權(quán),就可以被認(rèn)為是登出了。

總結(jié)

我認(rèn)為 JWTs 是一個(gè)在脫離 sessions 的情況下非常聰明的授權(quán)方式。它允許創(chuàng)建真正的服務(wù)端無狀態(tài)的基于 RESTful 的服務(wù),這也意味著不需要 session 存儲(chǔ)。

與瀏覽器自動(dòng)發(fā)送 session cookie 到任意匹配域/路徑組合(老實(shí)說,在大多數(shù)情況下這里只有域的情況)的 URL 不一樣的是,JWTs 可以選擇性的只向需要身份授權(quán)的資源來發(fā)送。

對(duì)于客戶端和服務(wù)端來說,它的實(shí)現(xiàn)非常簡(jiǎn)單,特別是已經(jīng)有專門的庫來制造簽名和驗(yàn)證令牌了。

感謝閱讀!

如果你喜歡這篇文章的話,歡迎分享它。同樣也十分歡迎你對(duì)它進(jìn)行評(píng)論!


上一篇:JSON Web Tokens的實(shí)現(xiàn)原理

下一篇:Cookie與Session

在線咨詢

點(diǎn)擊這里給我發(fā)消息 售前咨詢專員

點(diǎn)擊這里給我發(fā)消息 售后服務(wù)專員

在線咨詢

免費(fèi)通話

24小時(shí)免費(fèi)咨詢

請(qǐng)輸入您的聯(lián)系電話,座機(jī)請(qǐng)加區(qū)號(hào)

免費(fèi)通話

微信掃一掃

微信聯(lián)系
返回頂部