前言
JWT(JSON Web Token),是為了在網路環境間傳遞宣告而執行的一種基於JSON的開放標準(RFC 7519)。
更多的介紹和說明,以及各種原理,我在此就不多贅訴了。JWT不是一個新鮮的東西,網上相關的介紹已經非常多了。不是很瞭解的可以在網上搜尋一下相關資訊。
原始碼
Talk is cheap. Show me the code.
工作流程
JWT本質來說是一個token。在前後端進行HTTP連線時來進行相應的驗證。
- 部落格的後臺管理系統發起登入請求,後端伺服器校驗成功之後,生成JWT認證資訊;
- 前端接收到JWT後進行儲存;
- 前端在之後每次介面呼叫發起HTTP請求時,會將JWT放到HTTP的headers引數裡的authorization中一起傳送給後端;
- 後端接收到請求時會根據JWT中的資訊來校驗當前發起HTTP請求的使用者是否是具有訪問許可權的,有訪問許可權時則交給伺服器繼續處理,沒有時則直接返回401錯誤。
實現過程
1. 登入成功生成JWT
說明:以下程式碼只保留了核心程式碼,詳細程式碼可在對應檔案中檢視,下同。
// /server/api/admin/admin.controller.js
const jwt = require('jsonwebtoken');
const config = require('../../config/config');
exports.login = async(ctx) => {
// ...
if (hashedPassword === hashPassword) {
// ...
// 使用者token
const userToken = {
name: userName,
id: results[0].id
};
// 簽發token
const token = jwt.sign(userToken, config.tokenSecret, { expiresIn: '2h' });
// ...
}
// ...
}
複製程式碼
2. 新增中介軟體校驗JWT
// /server/middlreware/tokenError.js
const jwt = require('jsonwebtoken');
const config = require('../config/config');
const util = require('util');
const verify = util.promisify(jwt.verify);
/**
* 判斷token是否可用
*/
module.exports = function () {
return async function (ctx, next) {
try {
// 獲取jwt
const token = ctx.header.authorization;
if (token) {
try {
// 解密payload,獲取使用者名稱和ID
let payload = await verify(token.split(' ')[1], config.tokenSecret);
ctx.user = {
name: payload.name,
id: payload.id
};
} catch (err) {
console.log('token verify fail: ', err)
}
}
await next();
} catch (err) {
if (err.status === 401) {
ctx.status = 401;
ctx.body = {
success: 0,
message: '認證失敗'
};
} else {
err.status = 404;
ctx.body = {
success: 0,
message: '404'
};
}
}
}
}
複製程式碼
3. Koa.js中新增JWT處理
此處在開發時需要過濾掉登入介面(login),否則會導致JWT驗證永遠失敗。
// /server/config/koa.js
const jwt = require('koa-jwt');
const tokenError = require('../middlreware/tokenError');
// ...
const app = new Koa();
app.use(tokenError());
app.use(bodyParser());
app.use(koaJson());
app.use(resource(path.join(config.root, config.appPath)));
app.use(jwt({
secret: config.tokenSecret
}).unless({
path: [/^\/backapi\/admin\/login/, /^\/blogapi\//]
}));
module.exports = app;
複製程式碼
4.前端處理
前端開發使用的是Vue.js,傳送HTTP請求使用的是axios。
-
登入成功之後將JWT儲存到localStorage中(可根據個人需要儲存,我個人是比較喜歡儲存到localStorage中)。
methods: { login: async function () { // ... let res = await api.login(this.userName, this.password); if (res.success === 1) { this.errMsg = ''; localStorage.setItem('DON_BLOG_TOKEN', res.token); this.$router.push({ path: '/postlist' }); } else { this.errMsg = res.message; } } } 複製程式碼
-
Vue.js的router(路由)跳轉前校驗JWT是否存在,不存在則跳轉到登入頁面。
// /src/router/index.js router.beforeEach((to, from, next) => { if (to.meta.requireAuth) { const token = localStorage.getItem('DON_BLOG_TOKEN'); if (token && token !== 'null') { next(); } else { next('/login'); } } else { next(); } }); 複製程式碼
-
axios攔截器中給HTTP統一新增Authorization資訊
// /src/fetch/fetch.js axios.interceptors.request.use( config => { const token = localStorage.getItem('DON_BLOG_TOKEN'); if (token) { // Bearer是JWT的認證頭部資訊 config.headers.common['Authorization'] = 'Bearer ' + token; } return config; }, error => { return Promise.reject(error); } ); 複製程式碼
-
axios攔截器在接收到HTTP返回時統一處理返回狀態
// /src/main.js axios.interceptors.response.use( response => { return response; }, error => { if (error.response.status === 401) { Vue.prototype.$msgBox.showMsgBox({ title: '錯誤提示', content: '您的登入資訊已失效,請重新登入', isShowCancelBtn: false }).then((val) => { router.push('/login'); }).catch(() => { console.log('cancel'); }); } else { Vue.prototype.$message.showMessage({ type: 'error', content: '系統出現錯誤' }); } return Promise.reject(error); } ); 複製程式碼
總結
到這裡整個流程就走完了。當然單純的JWT並不是說絕對安全的,不過對於一個個人部落格系統的認證來說還是足夠的。
最後打個小廣告。目前正在開發新版的個人部落格中,包括前端和後端兩部分,都已在GitHub上開源,目前在逐步完善功能中。歡迎感興趣的同學fork和star。