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') } } ...