最近研究了下基於token的身份驗證,並將這種機制整合在我的項目中。如今不少網站的認證方式都從傳統的seesion+cookie轉向token校驗。對比傳統的校驗方式,token確實有更好的擴展性與安全性。
傳統的session+cookie身份驗證
因爲HTTP是無狀態的,它並不記錄用戶的身份。用戶將帳號與密碼發送給服務器後,後臺經過校驗,可是並無記錄狀態,因而下一次用戶的請求仍然須要校驗身份。爲了解決這一問題,須要在服務端生成一條包含用戶身份的記錄,也就是session,再將這條記錄發送給用戶並存儲在用戶本地,即cookie。接下來用戶的請求都會帶上這條cookie,若客戶端的cookie與服務端的session能對應上,則說明用戶身份驗證經過。
token身份校驗
流程大體以下:
第一次請求時,用戶發送帳號與密碼
後臺校驗經過,則會生成一個有時效性的token,再將此token發送給用戶
用戶得到token後,將此token存儲在本地,通常存儲在localstorage或cookie
以後的每次請求都會將此token添加在請求頭裏,全部須要校驗身份的接口都會被校驗token,若token解析後的數據包含用戶身份信息,則身份驗證經過。
對比傳統的校驗方式,token校驗有以下優點:
在基於token的認證,token經過請求頭傳輸,而不是把認證信息存儲在session或者cookie中。這意味着無狀態。你能夠從任意一種能夠發送HTTP請求的終端向服務器發送請求。
能夠避免CSRF攻擊
當在應用中進行 session的讀,寫或者刪除操做時,會有一個文件操做發生在操做系統的temp 文件夾下,至少在第一次時。假設有多臺服務器而且 session 在第一臺服務上建立。當你再次發送請求而且這個請求落在另外一臺服務器上,session 信息並不存在而且會得到一個「未認證」的響應。我知道,你能夠經過一個粘性 session 解決這個問題。然而,在基於 token 的認證中,這個問題很天然就被解決了。沒有粘性 session 的問題,由於在每一個發送到服務器的請求中這個請求的 token 都會被攔截。
下面介紹一下利用node+jwt(jwt教程)搭建簡易的token身份校驗
示例
當用戶第一次登陸時,提交帳號與密碼至服務器,服務器校驗經過,則生成對應的token,代碼以下:
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
//生成token的方法
function generateToken(data){
let created = Math.floor(Date.now() / 1000);
let cert = fs.readFileSync(path.join(__dirname, '../config/pri.pem'));//私鑰
let token = jwt.sign({
data,
exp: created + 3600 * 24
}, cert, {algorithm: 'RS256'});
return token;
}
//登陸接口
router.post('/oa/login', async (ctx, next) => {
let data = ctx.request.body;
let {name, password} = data;
let sql = 'SELECT uid FROM t_user WHERE name=? and password=? and is_delete=0', value = [name, md5(password)];
await db.query(sql, value).then(res => {
if (res && res.length > 0) {
let val = res[0];
let uid = val['uid'];
let token = generateToken({uid});
ctx.body = {
...Tips[0], data: {token}
}
} else {
ctx.body www.michenggw.com/= Tips[1006];
}
}).catch(e =www.yigouyule2.cn> {
ctx.body = www.mhylpt.com/ Tips[1002];
});
});
用戶經過校驗將獲取到的token存放在本地:
?
1
store.set('loginedtoken',token);//store爲插件
以後客戶端請求須要驗證身份的接口,都會將token放在請求頭裏傳遞給服務端:node
service.interceptors.request.use(config => {
let params = config.params |www.gcyl158.com| {};
let loginedtoken = store.get('loginedtoken');
let time = Date.now(www.gcyL157.com);
let {headers} = config;
headers = {.www.feifanyule.cn/..headers,loginedtoken};
params = {...params,_:time};
config = {...config,params,headers};
return config;
}, error => {
Promise.reject(error);
})
服務端對全部須要登陸的接口均攔截token並校驗合法性。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function verifyToken(token){
let cert = fs.readFileSync(path.join(__dirname, '../config/pub.pem'));//公鑰
try{
let result = jwt.verify(token, cert, {algorithms: ['RS256']}) || {};
let {exp = 0} = result,current = Math.floor(Date.now()/1000);
if(current <= exp){
res = result.data || {};
}
}catch(e){
}
return res;
}
app.use(async(ctx, next) => {
let {url = ''} = ctx;
if(url.indexOf('/user/') > -1){//須要校驗登陸態
let header = ctx.request.header;
let {loginedtoken} = header;
if (loginedtoken) {
let result = verifyToken(loginedtoken);
let {uid} = result;
if(uid){
ctx.state = {uid};
await next();
}else{
return ctx.body = Tips[1005];
}
} else {
return ctx.body = Tips[1005];
}
}else{
await next();
}
});
本示例使用的公鑰與私鑰可本身生成,操做以下:
打開命令行工具,輸入openssl,打開openssl;
生成私鑰:genrsa -out rsa_private_key.pem 2048
生成公鑰: rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pemweb