node實現基於token的身份驗證

最近研究了下基於token的身份驗證,並將這種機制整合在我的項目中。如今不少網站的認證方式都從傳統的seesion+cookie轉向token校驗。對比傳統的校驗方式,token確實有更好的擴展性與安全性。前端

傳統的session+cookie身份驗證

因爲HTTP是無狀態的,它並不記錄用戶的身份。用戶將帳號與密碼發送給服務器後,後臺經過校驗,可是並無記錄狀態,因而下一次用戶的請求仍然須要校驗身份。爲了解決這一問題,須要在服務端生成一條包含用戶身份的記錄,也就是session,再將這條記錄發送給用戶並存儲在用戶本地,即cookie。接下來用戶的請求都會帶上這條cookie,若客戶端的cookie與服務端的session能對應上,則說明用戶身份驗證經過。node

token身份校驗

流程大體以下:react

  • 第一次請求時,用戶發送帳號與密碼
  • 後臺校驗經過,則會生成一個有時效性的token,再將此token發送給用戶
  • 用戶得到token後,將此token存儲在本地,通常存儲在localstorage或cookie
  • 以後的每次請求都會將此token添加在請求頭裏,全部須要校驗身份的接口都會被校驗token,若token解析後的數據包含用戶身份信息,則身份驗證經過。

對比傳統的校驗方式,token校驗有以下優點:git

  • 在基於token的認證,token經過請求頭傳輸,而不是把認證信息存儲在session或者cookie中。這意味着無狀態。你能夠從任意一種能夠發送HTTP請求的終端向服務器發送請求。
  • 能夠避免CSRF攻擊
  • 當在應用中進行 session的讀,寫或者刪除操做時,會有一個文件操做發生在操做系統的temp 文件夾下,至少在第一次時。假設有多臺服務器而且 session 在第一臺服務上建立。當你再次發送請求而且這個請求落在另外一臺服務器上,session 信息並不存在而且會得到一個「未認證」的響應。我知道,你能夠經過一個粘性 session 解決這個問題。然而,在基於 token 的認證中,這個問題很天然就被解決了。沒有粘性 session 的問題,由於在每一個發送到服務器的請求中這個請求的 token 都會被攔截。

下面介紹一下利用node+jwt(jwt教程)搭建簡易的token身份校驗github

示例

當用戶第一次登陸時,提交帳號與密碼至服務器,服務器校驗經過,則生成對應的token,代碼以下:web

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 = Tips[1006];
        }
    }).catch(e => {
        ctx.body = Tips[1002];
    });
    
});
複製代碼

用戶經過校驗將獲取到的token存放在本地:sql

store.set('loginedtoken',token);//store爲插件
複製代碼

以後客戶端請求須要驗證身份的接口,都會將token放在請求頭裏傳遞給服務端:json

service.interceptors.request.use(config => {
    let params = config.params || {};
    let loginedtoken = store.get('loginedtoken');
    let time = Date.now();
    let {headers} = config;
    headers = {...headers,loginedtoken};
    params = {...params,_:time};
    config = {...config,params,headers};
    return config;
}, error => {
    Promise.reject(error);
})
複製代碼

服務端對全部須要登陸的接口均攔截token並校驗合法性。安全

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();
    }
});
複製代碼

本示例使用的公鑰與私鑰可本身生成,操做以下:bash

  1. 打開命令行工具,輸入openssl,打開openssl;
  2. 生成私鑰:genrsa -out rsa_private_key.pem 2048
  3. 生成公鑰: rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

閱讀原文 點此查看node後臺代碼

點此查看前端代碼

相關文章
相關標籤/搜索