背景
相信很多人在使用 postman 調(diào)用接口時(shí)都遇到了這樣的問題,就是請(qǐng)求的網(wǎng)站需要驗(yàn)證 JWT,而我們雖然可以一鍵生成,但每次生成后都要重新粘到 postman 的請(qǐng)求頭中才會(huì)生效,這不免帶來(lái)許多麻煩,更頭疼的是,大部分 JWT 的有效時(shí)間只有 10 分鐘,當(dāng)我們進(jìn)行其他工作,再回過神來(lái)調(diào)用接口時(shí),又會(huì)拋出令人討厭的 403 錯(cuò)誤,迫使我們?cè)俅问謩?dòng)生成,我們需要解決這個(gè)問題。
什么是 JWT
JWT 全稱 Json Web Token,字面意思就是 Json 對(duì)象在網(wǎng)絡(luò)中傳輸用到的令牌,而令牌的作用就是確保傳輸過程中的安全性。
授權(quán):這是使用 JWT 的最常見場(chǎng)景。用戶登錄后,每個(gè)后續(xù)請(qǐng)求都將包含 JWT,允許用戶訪問該令牌允許的路由、服務(wù)和資源。單點(diǎn)登錄是當(dāng)今廣泛使用 JWT 的一項(xiàng)功能,因?yàn)樗拈_銷小,并且能夠輕松地跨不同域使用。
信息交換:JSON Web 令牌是在各方之間安全地傳輸信息的好方法。由于 JWT 可以簽名(例如,使用公鑰/私鑰對(duì)),因此您可以確保發(fā)件人是他們所聲稱的身份。此外,由于簽名是使用標(biāo)頭和有效負(fù)載計(jì)算的,因此您還可以驗(yàn)證內(nèi)容是否未被篡改。
可以使用密鑰(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公鑰/私鑰對(duì)對(duì) JWT 進(jìn)行簽名。
官網(wǎng)地址:https://jwt.io/introduction
JWT 結(jié)構(gòu)
JWT 一般由三個(gè)部分組成:
- header
- payload
- signature
而由他們?nèi)齻€(gè)生成的JWT格式一般是xxxxx.yyyyy.zzzzz,中間由 . 進(jìn)行分隔,下面是一個(gè)真實(shí)的 JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NLZXlJZCI6ImlkYWFzbW5nXzAzMWYxMDc2OGE2ZjRhNGE5M2FiZWU2MDViYjgzNzg1IiwiZXhwIjoxNzMzNzkyMDkwfQ.AD_cguC8JPS9HIMHz0y0yWU--UYpgKnKRkd_SDpDSYU
header
header通常由兩部分組成:令牌的類型(JWT)和正在使用的簽名算法,例如 HMAC SHA256 或 RSA。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,此 JSON 經(jīng)過 Base64Url 編碼以形成 JWT 的第一部分。也就是xxxxx這一部分。
通常這一部分是保持不變的,因?yàn)檫@一部分信息并不涉及什么重要的安全信息,只是告知后臺(tái)應(yīng)當(dāng)采用什么算法進(jìn)行加密驗(yàn)簽。
payload
JWT 的第二部分是payload,其中包含聲明。聲明是關(guān)于實(shí)體(通常是用戶)和其他數(shù)據(jù)的聲明。 有三種類型的聲明:已注冊(cè)、公共和私有聲明。
- 已注冊(cè)的聲明 Register Claims:這些是一組預(yù)定義的聲明,不是強(qiáng)制性的,但建議使用,以提供一組有用的、可互操作的聲明。其中一些是:iss(頒發(fā)者)、exp(過期時(shí)間)、sub(主題)、aud(受眾)等。
請(qǐng)注意,聲明名稱只有三個(gè)字符長(zhǎng),因?yàn)?JWT 是緊湊的。
我們一般會(huì)攜帶 exp,因?yàn)?exp 的值是當(dāng)前時(shí)間加上有效期的一個(gè)時(shí)間類型值,所以每次經(jīng)過Base64加密生成的結(jié)果都不一樣。
-
公共聲明 Public Claims:這些聲明可以由使用 JWT 的用戶隨意定義。但為避免沖突,應(yīng)在 IANA JSON Web 令牌注冊(cè)表中定義它們,或?qū)⑵涠x為包含抗沖突命名空間的 URI。
-
私有聲明 Private Claims:這些是自定義聲明,用于在同意使用它們的各方之間共享信息,既不是注冊(cè)聲明,也不是公開聲明。
一個(gè)有效的payload示例:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后,對(duì)該 JSON 進(jìn)行 Base64Url 編碼,以形成 JWT 的第二部分。也就是yyyyy這一部分。
請(qǐng)注意,對(duì)于簽名令牌,此信息雖然可以防止篡改,但任何人都可以讀取。除非 JWT 已加密,否則不要將機(jī)密信息放在 JWT 的 payload 或 header 元素中。
signature
要?jiǎng)?chuàng)建signature部分,您必須獲取編碼的header、編碼的payload、密鑰、標(biāo)頭中指定的算法,并對(duì)其進(jìn)行簽名。
例如,如果您想使用 HMAC SHA256 算法,將按以下方式創(chuàng)建signature:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
最后,再次對(duì)HMAC SHA256 算法加密后得到的byte數(shù)組進(jìn)行base64加密,就得到了 JWT 的第三部分。也就是zzzzz這一部分。
signature用于驗(yàn)證消息在整個(gè)過程中沒有被更改,并且在使用私鑰secret簽名的令牌的情況下,它還可以驗(yàn)證 JWT 的發(fā)件人是否是它所聲稱的身份。
我們要怎么做
postman 中有這樣一個(gè)選項(xiàng) Scripts ,可以在里面編寫 JavaScript 代碼,左邊的 pre-request 和 post-response 則是指定在請(qǐng)求前的操作和接收到請(qǐng)求結(jié)果后的操作。( PS:舊版 postman 的 Scripts 應(yīng)該是 pre-request )

我們要做的,就是在這里面生成所需的 JWT ,并且放入 postman 的環(huán)境變量中,這樣,我們的接口在調(diào)用時(shí),只需要使用雙大括號(hào){{}}的的方式,就可以自動(dòng)從環(huán)境變量取到所需的 JWT 。
還記得上文提到的一個(gè)真實(shí)的 JWT 嗎?讓我們來(lái)分析一下。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NLZXlJZCI6ImlkYWFzbW5nXzAzMWYxMDc2OGE2ZjRhNGE5M2FiZWU2MDViYjgzNzg1IiwiZXhwIjoxNzMzNzkyMDkwfQ.AD_cguC8JPS9HIMHz0y0yWU--UYpgKnKRkd_SDpDSYU
第三部分AD_cguC8JPS9HIMHz0y0yWU--UYpgKnKRkd_SDpDSYU好像和前兩個(gè)部分不太一樣。
它攜帶了下劃線 _ 和減號(hào) - 這些字符,這是因?yàn)橄冉?jīng)過HMAC SHA256加密再經(jīng)過 base64 編碼后可能會(huì)產(chǎn)生加號(hào) + ,等于號(hào) = ,斜杠 / 這些不安全符號(hào),經(jīng)過轉(zhuǎn)義后就會(huì)產(chǎn)生第三部分的結(jié)果。
現(xiàn)在我們已經(jīng)知道了原理,進(jìn)入編碼部分。
首先,引入我們所需的類。其中 btoa 是一個(gè)安全的 base64 算法。
const CryptoJS = require('crypto-js')
const btoa = require('btoa')
完成 JWT 第一部分xxxxx,得到eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
let headerJson = JSON.stringify({
"alg": "HS256",
"typ": "JWT"
})
let header = btoa(headerJson)
注意:JSON 內(nèi)屬性的順序改變也會(huì)改變header
完成 JWT 第二部分yyyyy,得到eyJhY2Nlc3NLZXlJZCI6ImlkYWFzbW5nXzAzMWYxMDc2OGE2ZjRhNGE5M2FiZWU2MDViYjgzNzg1IiwiZXhwIjoxNzMzNzkyMDkwfQ
// 超時(shí)時(shí)間為10分鐘
let exp = new Date().getTime() + 10*60*1000
let ak = pm.environment.get("ak")
let payloadJson = JSON.stringify({
"accessKeyId": ak,
"exp": exp,
})
let payload = btoa(payloadJson);
這邊我使用到了aksk,其中公鑰ak放在payload內(nèi),私鑰sk放在第三部分用于HMAC SHA256加密
完成 JWT 第三部分zzzzz,得到AD_cguC8JPS9HIMHz0y0yWU--UYpgKnKRkd_SDpDSYU
function safeEncode(header,payload,sk) {
// HmacSHA256加密
let hash = CryptoJS.HmacSHA256(header+"."+payload,sk)
// base64加密
let base64 = CryptoJS.enc.Base64.stringify(hash);
let reg = new RegExp("/", "g");
// 手動(dòng)替換為可以被校驗(yàn)的符號(hào)
base64 = base64.replace(/=+/,"").replace(/\+/g,"-").replace(reg,"_");
return base64;
}
let signature=safeEncode(header,payload,sk)
最后的最后,就是拼接起來(lái),并放到環(huán)境變量中,就得到了每次都是隨機(jī)生成的 JWT,大功告成!
let jwt=header + "." + payload+"."+signature
pm.environment.set("SSO-JWT-Authorization",jwt)
完整代碼如下:
const CryptoJS = require('crypto-js')
const btoa = require('btoa') // 安全的base64加密算法
// 通過header,payload,sk生成簽名signature
function safeEncode(header,payload,sk) {
// HmacSHA256加密
let hash = CryptoJS.HmacSHA256(header+"."+payload,sk)
// base64加密
let base64 = CryptoJS.enc.Base64.stringify(hash);
let reg = new RegExp("/", "g");
// 手動(dòng)替換為可以被校驗(yàn)的符號(hào),
base64 = base64.replace(/=+/,"").replace(/\+/g,"-").replace(reg,"_");
return base64;
}
function generateToken() {
let ak=pm.environment.get("ak")// 放在aksk環(huán)境變量里,可以自行調(diào)整
let sk=pm.environment.get("sk")
let iat = new Date().getTime()
// 超時(shí)時(shí)間為10分鐘
let exp = iat + 10*60*1000
let headerJson = JSON.stringify({
"typ": "JWT",
"alg": "HS256"
})
let payloadJson=JSON.stringify({
"accessKeyId": ak,
"exp": exp,
})
let header=btoa(headerJson)
let payload=btoa(payloadJson)
let signature=safeEncode(header,payload,sk)
// 拼接得到j(luò)wt
let jwt=header + "." + payload+"."+signature
pm.environment.set("SSO-JWT-Authorization",jwt)
console.log(jwt)
}
generateToken()
浙公網(wǎng)安備 33010602011771號(hào)