本文轉自:http://www.javashuo.com/article/p-anctkgwx-ck.htmljavascript
在先後端分離的開發中,經過 Restful API 進行數據交互時,若是沒有對 API 進行保護,那麼別人就能夠很容易地獲取並調用這些 API 進行操做。那麼服務器端要如何進行鑑權呢?html
Json Web Token 簡稱爲 JWT,它定義了一種用於簡潔、自包含的用於通訊雙方之間以 JSON 對象的形式安全傳遞信息的方法。JWT 可使用 HMAC 算法或者是 RSA 的公鑰密鑰對進行簽名。前端
說得好像跟真的同樣,那麼到底要怎麼進行認證呢?vue
首先用戶登陸時,輸入用戶名和密碼後請求服務器登陸接口,服務器驗證用戶名密碼正確後,生成token並返回給前端,前端存儲token,並在後面的請求中把token帶在請求頭中傳給服務器,服務器驗證token有效,返回正確數據。java
既然服務器端使用 Koa2 框架進行開發,除了要使用到 jsonwebtoken 庫以外,還要使用一個 koa-jwt 中間件,該中間件針對 Koa 對 jsonwebtoken 進行了封裝,使用起來更加方便。下面就來看看是如何使用的。ios
這裏註冊了個 /login
的路由,用於用戶登陸時獲取token。git
const router = require('koa-router')(); const jwt = require('jsonwebtoken'); const userModel = require('../models/userModel.js'); router.post('/login', async (ctx) => { const data = ctx.request.body; if(!data.name || !data.password){ return ctx.body = { code: '000002', data: null, msg: '參數不合法' } } const result = await userModel.findOne({ name: data.name, password: data.password }) if(result !== null){ 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: '用戶名或密碼錯誤' } } }); module.exports = router;
在驗證了用戶名密碼正確以後,調用 jsonwebtoken 的 sign() 方法來生成token,接收三個參數,第一個是載荷,用於編碼後存儲在 token 中的數據,也是驗證 token 後能夠拿到的數據;第二個是密鑰,本身定義的,驗證的時候也是要相同的密鑰才能解碼;第三個是options,能夠設置 token 的過時時間。github
接下來就是前端獲取 token,這裏是在 vue.js 中使用 axios 進行請求,請求成功以後拿到 token 保存到 localStorage 中。這裏登陸成功後,還把當前時間存了起來,除了判斷 token 是否存在以外,還能夠再簡單的判斷一下當前 token 是否過時,若是過時,則跳登陸頁面web
submit(){
axios.post('/login', { name: this.username, 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; })
經過 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。
咱們上面使用 jsonwebtoken 的 sign() 方法來生成 token 的,那麼 koa-jwt 作了些什麼幫咱們來驗證 token。
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 錯誤狀態。
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 無效,則會在此方法被驗證出來。
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 基本的流程,這或許不是最完美的,但在大多數登陸中使用已經足夠了。
上面的代碼可能不夠具體,這裏使用 Koa + mongoose + vue.js 實現的一個例子 : jwt-demo,能夠作爲參考。