實現JWT鑑權機制
一、是什麼
JWT(JSON Web Token),本質就是一個字串書寫規範,如下圖,作用是用來在使用者和伺服器之間傳遞安全可靠的資訊
在目前前後端分離的開發過程中,使用token
鑑權機制用於身份驗證是最常見的方案,流程如下:
- 伺服器當驗證使用者賬號和密碼正確的時候,給使用者頒發一個令牌,這個令牌作為後續使用者訪問一些介面的憑證
- 後續訪問會根據這個令牌判斷使用者時候有許可權進行訪問
Token
,分成了三部分,頭部(Header)、載荷(Payload)、簽名(Signature),並以.
進行拼接。其中頭部和載荷都是以JSON
格式存放資料,只是進行了編碼
header
每個JWT都會帶有頭部資訊,這裡主要宣告使用的演算法。宣告演算法的欄位名為alg
typ
的欄位,預設JWT
即可。以下示例中演算法為HS256
{"alg":"HS256","typ":"JWT"}
因為JWT是字串,所以我們還需要對以上內容進行Base64編碼,編碼後字串如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload
載荷即訊息體,這裡會存放實際的內容,也就是Token
的資料宣告,例如使用者的id
和name
,預設情況下也會攜帶令牌的簽發時間iat
,通過還可以設定過期時間,如下:
{
"sub":"1234567890",
"name":"JohnDoe",
"iat":1516239022
}
同樣進行Base64編碼後,字串如下:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Signature
簽名是對頭部和載荷內容進行簽名,一般情況,設定一個secretKey
,對前兩個的結果進行HMACSHA25
演算法,公式如下:
Signature=HMACSHA256(base64Url(header)+.+base64Url(payload),secretKey)
一旦前面兩部分資料被篡改,只要伺服器加密用的金鑰沒有洩露,得到的簽名肯定和之前的簽名不一致
二、如何實現
Token
的使用分成了兩部分:
- 生成token:登入成功的時候,頒發token
- 驗證token:訪問某些資源或者介面時,驗證token
生成 token
藉助第三方庫jsonwebtoken
,通過jsonwebtoken
的sign
方法生成一個token
:
-
第一個引數指的是 Payload
-
第二個是祕鑰,服務端特有
-
第三個引數是 option,可以定義 token 過期時間
constcrypto=require("crypto"),
jwt=require("jsonwebtoken");
//TODO:使用資料庫
//這裡應該是用資料庫儲存,這裡只是演示用
letuserList=[];
classUserController{
//使用者登入
staticasynclogin(ctx){
constdata=ctx.request.body;
if(!data.name||!data.password){
returnctx.body={
code:"000002",
message:"引數不合法"
}
}
constresult=userList.find(item=>item.name===data.name&&item.password===crypto.createHash('md5').update(data.password).digest('hex'))
if(result){
//生成token
consttoken=jwt.sign(
{
name:result.name
},
"test_token",//secret
{expiresIn:60*60}//過期時間:60* 60 s
);
returnctx.body={
code:"0",
message:"登入成功",
data:{
token
}
};
}else{
returnctx.body={
code:"000002",
message:"使用者名稱或密碼錯誤"
};
}
}
}
module.exports=UserController;
在前端接收到token
後,一般情況會通過localStorage
進行快取,然後將token
放到HTTP
請求頭Authorization
中,關於Authorization
的設定,前面要加上 Bearer ,注意後面帶有空格
axios.interceptors.request.use(config=>{
consttoken=localStorage.getItem('token');
config.headers.common['Authorization']='Bearer'+token;//留意這裡的Authorization
returnconfig;
})
校驗token
使用koa-jwt
中介軟體進行驗證,方式比較簡單
/注意:放在路由前面
app.use(koajwt({
secret:'test_token'
}).unless({//配置白名單
path:[/\/api\/register/,/\/api\/login/]
}))
- secret 必須和 sign 時候保持一致
- 可以通過 unless 配置介面白名單,也就是哪些 URL 可以不用經過校驗,像登陸/註冊都可以不用校驗
- 校驗的中介軟體需要放在需要校驗的路由前面,無法對前面的 URL 進行校驗
獲取token
使用者的資訊方法如下:
router.get('/api/userInfo',async(ctx,next)=>{
constauthorization=ctx.header.authorization//獲取jwt
consttoken=authorization.replace('Beraer','')
constresult=jwt.verify(token,'test_token')
ctx.body=result
注意:上述的HMA256
加密演算法為單祕鑰的形式,一旦洩露後果非常的危險
在分散式系統中,每個子系統都要獲取到祕鑰,那麼這個子系統根據該祕鑰可以釋出和驗證令牌,但有些伺服器只需要驗證令牌
這時候可以採用非對稱加密,利用私鑰釋出令牌,公鑰驗證令牌,加密演算法可以選擇RS256
三、優缺點
優點:
- json具有通用性,所以可以跨語言
- 組成簡單,位元組佔用小,便於傳輸
- 服務端無需儲存會話資訊,很容易進行水平擴充套件
- 一處生成,多處使用,可以在分散式系統中,解決單點登入問題
- 可防護CSRF攻擊
缺點:
- payload部分僅僅是進行簡單編碼,所以只能用於儲存邏輯必需的非敏感資訊
- 需要保護好加密金鑰,一旦洩露後果不堪設想
- 為避免token被劫持,最好使用https協議
參考文獻
- http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
- https://blog.wangjunfeng.com/post/golang-jwt/
- https://vue3js.cn/interview/