npm i -S crypto-js
// 先切換到/server
目錄下npm i -S koa-jwt
// 先切換到/server
目錄下npm i -S jsonwebtoken
// 先切換到/server
目錄下JWT
對象爲一個長字串,字符之間經過"."分隔符分爲三個子串。JWT
的三個部分:JWT頭、有效載荷和簽名。
一旦JWT
簽發,在有效期內將會一直有效。前端
JWT
使用的核心步驟vue
jsonwebtoken
對載荷進行簽名,jsonwebtoken
結合koa-jwt
進行簽名認證,最終獲得簽名前的數據ctx.state[<key>]
// 新建文件:/server/config/auth.js const AES = require("crypto-js/aes"); const secretText = 'jwt.secret.text'; const key = 'jwt.secret.key' // // Encrypt // var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString(); // // Decrypt // var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123'); // var originalText = bytes.toString(CryptoJS.enc.Utf8); // console.log(originalText); // 'my message' module.exports = { secret: AES.encrypt(secretText, key).toString(), authKey: 'auth' }
能夠經過ctx.state[<authKey>]
,即ctx.state.auth
獲取有效荷載。node
// 新建文件:server/utils/auth.js const jwt = require('jsonwebtoken') const { secret } = require('../config/auth') // 定義超時時間:token的有效時長 const expiresIn = '2h'; module.exports = { sign: function(payload) { // 推薦對payload進行加密。 const token = jwt.sign(payload, secret, { expiresIn }); return token; }, vertify: function(ctx, decodeToken, token){ } }
// 更新文件:server/utils/auth.js ... vertify: function(ctx, decodeToken, token){ let result = true; try{ jwt.verify(token, secret); result = false; }catch(e) { } return result; } ...
vertify
返回true
代表token
已無效或認證失敗;vertify
返回false
代表token
仍有效,且已經成功;git
//更新文件:server/app.js ... const koaJwt = require('koa-jwt'); const { secret, authKey } = require('./config/auth'); const { vertify } = require('./utils/auth'); ... app.use(koaJwt({ //要放到路由前邊,不然,無效 secret, key: authKey, // jwt是否被廢除 isRevoked: vertify })) ...
koa-jwt
幫助實現認證邏輯,認證失敗,拋出錯誤。koa-jwt
相似koa-body
,將有效載荷解析,存儲到ctx.state[<authKey>]
(ctx.state.auth
)中。token
// 更新文件: ... const auth = require('../utils/auth'); ... async function login (ctx) { ... if(user) { const token = auth.sign({ id: user.id, account }) ctx.body = { code: '200', data: { token, id: user.id, account, alias: user.alias }, msg: '登錄成功' } } ... }
Postman
測試結果
vs code
調試控制檯
上一步認證失敗,拋出了ERROR
經過斷點能夠查看到錯誤信息,補充異常邏輯
官方推薦經過status
判斷是不是認證錯誤
github
// 更新文件:server/app.js ··· // 中間件的錯誤處理 app.use(function(ctx, next){ return next().catch((err) => { console.log(err) if (err.name === 'ValidationError') { ctx.body = { code: '403', data: null, msg: err.message } } else if (401 == err.status) { //認證錯誤 ctx.status = 401; // 更新HTTP Response Code ctx.body = { code: '401', data: null, msg: `${err.message}\n請先登錄` } } else { throw err; } }); }); ···
Postman
繼續測試
登陸還須要認證?答案是不須要的。
不單單是登陸,註冊及其後續的靜態圖片訪問,都不須要認證。web
// 更新文件:server/app.js secret, key: authKey, // jwt是否被廢除 isRevoked: vertify }).unless({ // 返回true就是忽略認證 custom: function(ctx) { const { method, path, query } = ctx; if(path === '/'){ return true; } if(path === '/users' && query.action) { return true; } return false; } }));
unless
,返回true
即爲忽略認證。unless
的具體用法同koa-unless
,這裏使用的custom
是自定義忽略規則。Postman
繼續測試
成功,完美。vuex
Postman
測試身份認證然而,用Postman
測試其它接口:
這是由於沒有在請求頭Headers
里加上認證信息Authorization
。
在登陸接口的Tests
面板,藉助右邊的提示,設置全局變量token
爲登陸返回數據的token
。npm
// 更新Tests面板內容: pm.test("Your test name", function () { var jsonData = pm.response.json(); pm.globals.set("token", jsonData.data.token); });
在須要身份認證的測試接口的Authorization
面板中,選擇Type
爲Bearer Token
,值爲變量{{token}}
。json
到目前爲止,接口沒有什麼問題了,但,前端頁面請求,沒有添加Authorization
。app
// 更新文件:client/src/views/login/index.vue ... async function onLogin () { ... if (res && res.code === '200') { const {token, ...user} = res.data // 新增 localStorage.setItem('token', res.data.token) // 新增 this.$store.commit('putLoginer', user) this.$router.replace('/home') } ... } ...
在登陸時,獲取token
,進行本地存儲。
// 更新文件:client/src/utils/http.js ... instance.interceptors.request.use(async (config) => { const token = await localStorage.getItem('token') token && (config.headers['Authorization'] = `Bearer ${token}`) return config }, function (error) { console.log('------request===========', error) // Do something with request error return Promise.reject(error) }) ...
token
存在時,將token
賦值給config.headers['Authorization']
。Bearer
爲前綴的,這是規範,要求這樣處理。// 僅爲了測試身份認證,測試完,就把該文件還原。 // 更新文件:client/src/views/homePage/index.vue ... <script> import http from '@/utils/http' export default { async created () { const res = await http.get('/users') console.log(res) } } </script> ...
測試結果:
前端認證失敗,就退回到登陸/
頁面,清空本地存儲和vuex
。(登出時,也是這些步驟,登出邏輯之前已經處理過了)
// 更新文件:client/src/utils/http.js ... import router from '@/router' import store from '@/store' ... instance.interceptors.response.use( async res => { if (/^20./.test(res.status)) { return res.data } if (/^40./.test(res.status)) { router.push('/') await localStorage.clear() store.commit('resetVuex') return { code: '401', msg: '請從新登錄' } } console.log('------response=======', res) return res }, error => { return Promise.reject(error) } )
測試結果以下:退回到登陸/
頁面,清空本地存儲和vuex
至於爲何調用兩次/users
接口,由於/home
下認證一次,/login
調用了一次。
這裏改進一下/login
的調用,讓若是有token
的時候,認證成功,直接進入首頁。(偷懶:專門作一個認證接口比較好)
// 更新文件:client/src/views/login/index.vue ... async created () { const res = await http.get('/users') if (res.code === '200') { this.$router.replace('/home') } } ...