koa2學習筆記(七)使用JWT鑑權

在前段時間,遇到一個需求,使用token進行單一登錄,網上都資料也是比較久遠了,不知道是否有效,在我一個一個都嘗試下,終於找到一個實測有效的demo。前端

什麼是token登錄

Json Web Token 簡稱爲 JWT,它定義了一種用於簡潔、自包含的用於通訊雙方之間以 JSON 對象的形式安全傳遞信息的方法。JWT 可使用 HMAC 算法或者是 RSA 的公鑰密鑰對進行簽名。vue

認證過程 ios

首先用戶登陸時,輸入用戶名和密碼後請求服務器登陸接口,服務器驗證用戶名密碼正確後,生成token並返回給前端,前端存儲token,並在後面的請求中把token帶在請求頭中傳給服務器,服務器驗證token有效,返回正確數據。web

既然服務器端使用 Koa2 框架進行開發,除了要使用到 jsonwebtoken 庫以外,還要使用一個 koa-jwt 中間件,該中間件針對 Koa 對 jsonwebtoken 進行了封裝,使用起來更加方便。下面就來看看是如何使用的。算法

生成token

註冊一個/login的路由,用戶登錄時獲取token數據庫

app.js

const Koa = require('koa')
const jwt = require('jsonwebtoken')
const Router = require('koa-router')

const app = new Koa()
const router = new Router()

router.get('/login',async (ctx,next)=>{
    const data = ctx.request.body;
    if(!data.name || !data.password){
        return ctx.body = {
            code: '000002',
			data: null,
			msg: '參數不合法'
        }
    }
    
    // 由於要涉及到數據庫,我這裏模擬假數據來進行登錄
    const userInfo = {
        name: 'cbq123',
        password: '123456'
    }
    
    // 登錄校驗
    if(data.name == userInfo.name && data.password == userInfo.password){
        const token = jwt.sign({
			name: result.name,
			_id: result._id
		}, 'my_token', { expiresIn: '2h' });
		return ctx.body = {
			code: '000001',
			data: token,
			msg: '登陸成功'
		}
    }else{
        return ctx.body = {
			code: '000002',
			data: null,
			msg: '用戶名或密碼錯誤'
		}
    }
    
})

// koa-router啓用,不太瞭解到話,能夠看看這裏https://www.npmjs.com/package/koa-router
app.use(router.routes())
   .use(router.allowedMethods())
   
app.listen(3000,()=>{
    console.log('服務已啓動在3000端口')   
})
複製代碼

在驗證了用戶名密碼正確以後,調用 jsonwebtoken 的 sign() 方法來生成token,接收三個參數,第一個是載荷,用於編碼後存儲在 token 中的數據,也是驗證 token後能夠拿到的數據;第二個是密鑰,本身定義的,驗證的時候也是要相同的密鑰才能解碼;第三個是options,能夠設置 token 的過時時間。npm

獲取token

接下來就是前端獲取 token,這裏是在vue.js中使用axios進行請求,請求成功以後拿到 token 保存到 localStorage中。這裏登陸成功後,還把當前時間存了起來,除了判斷token是否存在以外,還能夠再簡單的判斷一下當前token是否過時,若是過時,則跳登陸頁面json

一個登錄的提交方法axios

submit(){
	axios.post('/login', {
		name: this.name,
		password: this.password
	}).then(res => {
		if(res.code === '000001'){
			localStorage.setItem('token', res.data);
			localStorage.setItem('token_exp', new Date().getTime());
			this.$router.push('/');
		}else{
			alert(res.msg);
		}
	})
}
複製代碼

而後請求服務器端API的時候,把token帶在請求頭中傳給服務器進行驗證。每次請求都要獲取 localStorage 中的token,這樣很麻煩,這裏使用了axios的請求攔截器,對每次請求都進行了取 token 放到 headers 中的操做。安全

axios.interceptors.request.use(config => {
    const token = localStorage.getItem('token');
    config.headers.common['Authorization'] = 'Bearer ' + token;
    return config;
})
複製代碼

驗證token

經過 koa-jwt 中間件來進行驗證,用法也很是簡單

const koa = require('koa');
const koajwt = require('koa-jwt');
const app = new koa();
// 錯誤處理
app.use((ctx, next) => {
    return next().catch((err) => {
        if(err.status === 401){
            ctx.status = 401;
      		ctx.body = 'Protected resource, use Authorization header to get access\n';
        }else{
            throw err;
        }
    })
})
app.use(koajwt({
	secret: 'my_token'
}).unless({
	path: [/\/user\/login/]
}));
複製代碼

經過 app.use 來調用該中間件,並傳入密鑰 {secret: 'my_token'},unless 能夠指定哪些 URL 不須要進行 token 驗證。token 驗證失敗的時候會拋出401錯誤,所以須要添加錯誤處理,並且要放在 app.use(koajwt()) 以前,不然不執行。

若是請求時沒有token或者token過時,則會返回401。

解析koa-jwt

咱們上面使用 jsonwebtoken 的 sign() 方法來生成 token 的,那麼 koa-jwt 作了些什麼幫咱們來驗證 token。

// resolvers/auth-header.js

module.exports = function resolveAuthorizationHeader(ctx, opts) {
    if (!ctx.header || !ctx.header.authorization) {
        return;
    }
    const parts = ctx.header.authorization.split(' ');
    if (parts.length === 2) {
        const scheme = parts[0];
        const credentials = parts[1];
        if (/^Bearer$/i.test(scheme)) {
            return credentials;
        }
    }
    if (!opts.passthrough) {
        ctx.throw(401, 'Bad Authorization header format. Format is "Authorization: Bearer <token>"');
    }
};
複製代碼

在 auth-header.js 中,判斷請求頭中是否帶了 authorization,若是有,將 token 從 authorization 中分離出來。若是沒有 authorization,則表明了客戶端沒有傳 token 到服務器,這時候就拋出 401 錯誤狀態。

// verify.js

const jwt = require('jsonwebtoken');
module.exports = (...args) => {
    return new Promise((resolve, reject) => {
        jwt.verify(...args, (error, decoded) => {
            error ? reject(error) : resolve(decoded);
        });
    });
};
複製代碼

在 verify.js 中,使用 jsonwebtoken 提供的 verify() 方法進行驗證返回結果。jsonwebtoken 的 sign() 方法來生成 token 的,而 verify() 方法則是用來認證和解析 token。若是 token 無效,則會在此方法被驗證出來。

// index.js
const decodedToken = await verify(token, secret, opts);
if (isRevoked) {
	const tokenRevoked = await isRevoked(ctx, decodedToken, token);
	if (tokenRevoked) {
		throw new Error('Token revoked');
	}
}
ctx.state[key] = decodedToken;  // 這裏的key = 'user'
if (tokenKey) {
	ctx.state[tokenKey] = token;
}
複製代碼

在 index.js 中,調用 verify.js 的方法進行驗證並解析 token,拿到上面進行 sign() 的數據 {name: result.name, _id: result._id},並賦值給 ctx.state.user,在控制器中即可以直接經過 ctx.state.user 拿到 name 和 _id。

安全性

  • 若是 JWT 的加密密鑰泄露的話,那麼就能夠經過密鑰生成 token,隨意的請求 API 了。所以密鑰絕對不能存在前端代碼中,否則很容易就能被找到。

  • 在 HTTP 請求中,token 放在 header 中,中間者很容易能夠經過抓包工具抓取到 header 裏的數據。而 HTTPS 即便能被抓包,可是它是加密傳輸的,因此也拿不到 token,就會相對安全了。

最後

以上內容要感謝林鑫博客,原文地址在Koa2 使用 JWT 進行鑑權

小強前端交流羣QQ羣:724179055

定時分析技術和資料,歡迎你們進來一塊兒交流。

往期回顧地址:

相關文章
相關標籤/搜索