!!本文使用nuxt.js、koa2做爲開發環境,前端存儲token的方式是cookie前端
JSON Web Token - 在Web應用間安全地傳遞信息ios
先npm安裝咱們所需的模塊web
npm install koa-jwt jsonwebtoken cookieparser
注:koa-jwt是使用jsonwebtoken來進行令牌(token)對發放與驗證對,cookieparser模塊咱們用來解析cookie
而後貼一個nuxt應用框架以koa爲後臺的初始index.js文件npm
const Koa = require("koa"); const consola = require("consola"); const { Nuxt, Builder } = require("nuxt"); const app = new Koa(); // Import and Set Nuxt.js options const config = require("../nuxt.config.js"); config.dev = app.env !== "production"; async function start() { // Instantiate nuxt.js const nuxt = new Nuxt(config); const { host = process.env.HOST || "127.0.0.1", port = process.env.PORT || 3000 } = nuxt.options.server; // Build in development if (config.dev) { const builder = new Builder(nuxt); await builder.build(); } else { await nuxt.ready(); } app.use((ctx, next) => { ctx.status = 200; ctx.respond = false; // Bypass Koa's built-in response handling ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash nuxt.render(ctx.req, ctx.res); }); app.listen(port, host); consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }); } start();
咱們在index.js中引入koa-jwt並使用它json
const jwt = require("koa-jwt"); const SECRET = "Public secret"; app.use( jwt({ secret: SECRET }).unless({ path: \[/^\\/api\\/login/, /^\\/api\\/register/\] }) );
注:unless是jwt的一個排除對應的url鑑權的方法,咱們排除了login與logout路由鑑權,SECRET是保存在後臺用來讓jsonwebtoken生成token字符串的密鑰
在你的接口文件中引入jsonwebtokenaxios
const jsonwebtoken = require("jsonwebtoken");
在/api/login登陸接口中若是登陸驗證成功則生成令牌併發送給前端,並在cookie中設置值後端
if(result){ let token = jsonwebtoken.sign(payload,SECRET, { expiresIn: "1h" } ); ctx.cookies.set("auth", JSON.stringify(token), { maxAge: 60000 * 60, overwrite: true }); ctx.body={ token: token, user: {} } }
注:payload表示有效負載,若是你有看上面那篇有關jsonwebtoken的文章,我想你是明白的~ SECRET則是咱們在index.js文件相同的密鑰字符串,expiresIn是令牌有效時間,字符串形式,能夠放入1h,或者數字(毫秒),這裏有隨便提一下,cookie咱們的httponly選項要是true(默認是true)這是爲了防XSS(多少能防些)就不細說
這時候咱們的index.js文件就變成來這樣(api接口文件代碼我就不貼出來了):api
const Koa = require("koa"); const consola = require("consola"); const { Nuxt, Builder } = require("nuxt"); const bodyParser = require("koa-bodyparser"); const jwt = require("koa-jwt"); //引入登陸接口文件 const user = require("./api/user"); const SECRET = "Public secret"; const app = new Koa(); // Import and Set Nuxt.js options const config = require("../nuxt.config.js"); config.dev = app.env !== "production"; async function start() { // Instantiate nuxt.js const nuxt = new Nuxt(config); const { host = process.env.HOST || "127.0.0.1", port = process.env.PORT || 3000 } = nuxt.options.server; // Build in development if (config.dev) { const builder = new Builder(nuxt); await builder.build(); } else { await nuxt.ready(); } app.use( jwt({ secret: SECRET }).unless({ path: \[/^\\/api\\/login/, /^\\/api\\/register/\] }) ); //使用接口文件 app.use(user.routes()).use(user.allowedMethods()); app.use((ctx, next) => { ctx.status = 200; ctx.respond = false; // Bypass Koa's built-in response handling ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash nuxt.render(ctx.req, ctx.res); }); app.listen(port, host); consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }); } start();
如今咱們已經作好後臺的鑑權邏輯了,只要有請求進來除了/api/login與/api/register的url外都會先通過jwt進行權限安全
前端的處理邏輯是登陸成功後從返回的結果中將用戶信息保存在store中,而後在axios中添加請求攔截器,每次請求的時候在請求頭中添加authorization字段,字段內容是"Bearer "+token,Bearer與token之間有空格
,添加錯誤攔截器,jsonwebtoken在驗證令牌錯誤時會發出錯誤,且相應code爲401。這裏我直接貼代碼了,就不細說了服務器
export default function(app) { let axios = app.$axios; axios.onRequest(config => { if (app.store.state.authUser) { config.headers.authorization = "Bearer " + app.store.state.authUser.token; } }); axios.onError(error => { const code = parseInt(error.response && error.response.status); if (code === 401) { //錯誤處理邏輯 } }); }
const cookieparser = process.server ? require("cookieparser") : undefined; export const state = () => ({ authUser: null }); export const mutations = { SET_USER(state, user) { state.authUser = user; } }; export const actions = { // nuxtServerInit是由Nuxt.js在服務器渲染每一個頁面以前調用的 nuxtServerInit({ commit }, { req }) { let auth = null; if (req.headers.cookie) { const parsed = cookieparser.parse(req.headers.cookie); auth = new Buffer( parsed.authUser.split(".")[1], "base64" ).toString(); try { auth = JSON.parse(auth); auth.token = parsed.authUser; // console.log(auth); commit("SET_USER", auth); } catch (err) { // 未找到有效的cookie console.log(err); } } }, async login({ commit }, { username, password }) { try { const { data } = await this.$axios.post("/api/login", { name: username, password: password }); if (data.result) { commit("SET_USER", data); } } catch (error) { if (error.response && error.response.status === 401) { throw new Error("Bad credentials"); } throw error; } }, };
注:cookieparser模塊用來解析cookie
至此,咱們的路由鑑權就已經弄好了,完結撒花🎉🎉
路由鑑權是沒問題了,可是若是我整個站的連接那麼多,我不能只有/api/login跟/api/logout不用鑑權,其餘的都要,我一個個都往unless裏面加?也能夠,可是以爲有點麻煩。還有就是若是令牌到期了怎麼辦,我要是在後臺編輯着東西,忽然權限過時了,那不是很尷尬。因此咱們須要只要在限制的時間內有操做後臺,就更新令牌。
咱們修改一下後臺koa中間件,直接貼index.js代碼
const Koa = require("koa"); const consola = require("consola"); const { Nuxt, Builder } = require("nuxt"); const bodyParser = require("koa-bodyparser"); const jsonwebtoken = require("jsonwebtoken"); //引入登陸接口文件 const user = require("./api/user"); const SECRET = "Public secret"; const app = new Koa(); // Import and Set Nuxt.js options const config = require("../nuxt.config.js"); config.dev = app.env !== "production"; async function start() { // Instantiate nuxt.js const nuxt = new Nuxt(config); const { host = process.env.HOST || "127.0.0.1", port = process.env.PORT || 3000 } = nuxt.options.server; // Build in development if (config.dev) { const builder = new Builder(nuxt); await builder.build(); } else { await nuxt.ready(); } //修改的地方在這裏 app.use((ctx, next) => { if ( ctx.url.match(/^\/api\/login/) || ctx.url.match(/^\/api\/register/) ) { return next(); } if (ctx.url.match(/^\/api/)) { //路由判斷是否以/api開頭的url,是則進行鑑權,不然直接輸入內容 let authorization = ctx.headers.authorization, token; if (ctx.headers) { if (!authorization) { ctx.status = 401; return (ctx.body = "Bad permissions"); } token = authorization.split(" ")[1]; try { let decoded = jsonwebtoken.verify(token, SECRET),refresh; //這裏寫刷新令牌邏輯,refresh是判斷是否到達刷新令牌時間結果 if (refresh) { //在刷新時間範圍內請求則刷新令牌 try { let newToken = jsonwebtoken.sign( user, SECRET, { expiresIn: "1h" } ); ctx.cookies.set( "authUser", JSON.stringify(newToken), { maxAge: 60000 * 60, overwrite: true } ); } catch (err) { console.log(err); } } } catch (err) { ctx.status = 401; return (ctx.body = "Bad permissions"); } } return next(); } else { ctx.status = 200; ctx.respond = false; // Bypass Koa's built-in response handling ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash nuxt.render(ctx.req, ctx.res); } }); app.use(user.routes()).use(user.allowedMethods()); app.listen(port, host); consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }); } start();
如今終於完了,咱們作到了只要請求除了/api/login與/api/register以外都任意以/api開頭的url都會進行鑑權,令牌也會持續更新不怕用着用着就過時了。
隨便說說刷新令牌邏輯,我用的是設置令牌過時時間爲2小時,刷新令牌的時間是1小時,也就是隻要你離過時1小時前不操做後臺,令牌就會過時,以當前時間是否超過令牌發放時間加一小時的和來判斷是否須要刷新令牌,即currentTime > refreshInterval+iat;結果是true則刷新令牌。
若有錯誤,勞煩請指出,謝謝🙏🙏