废话部分
上次废话太多,估计有些小伙伴没看太清楚。这次会精简很多。只拿关键的出来讲。
个人属于前端,后端看到了扣下留情。希望本文能帮助大家
项目地址:
前端:https://github.com/ht-sauce/dream
后端:https://github.com/ht-sauce/dream-admin
一、权鉴选型
这块没太多可以说的,基本百度的结果就是jwt
而eggjs官方是egg-jwt,其本质也是jsonwebtoken
egg-jwt地址:https://www.npmjs.com/package/egg-jwt
jsonwebtoken地址:https://www.npmjs.com/package/jsonwebtoken
eggjs的插件配置方面我就不多说了。太简单了(再次赞美eggjs)。
二、先写登录接口,保证生成权鉴信息
1、先放完整的登录接口函数
// 登录接口 async login() { const { ctx, service } = this; const userBusiness = service.consumer.user; const query = ctx.request.body; // 接口请求数据 // 参数校验 const rule = { account: { type: 'string', required: true }, // 有format才能有message信息 password: { type: 'string', required: true }, }; try { await ctx.validate(rule, query); // 优先处理用户是否存在 const login = await userBusiness.userLogin(query); if (login) { // 登录之后生成口令,有效期24小时 const token = ctx.helper.generate_token(query.account); // 前端肯定会需要用户信息,返回给前端用户基本信息 const userInfo = { userInfo: await userBusiness.find(query), sign: token, }; ctx.body = ctx.helper.result(userInfo); } else { ctx.body = ctx.helper.result('', -1, '用户不存在请注册'); } } catch (e) { ctx.body = ctx.helper.result('', -1, e); } }
2、原理解析
主要是先判断用户名密码,然后再是用自己封装的helper(eggjs的扩展)来生成口令。
至于helper就是进行统一的封装函数处理。
然后一并丢给前端。
三、中间件解析口令,进行校验拦截
1、中间件代码
'use strict'; module.exports = (options, app) => { return async function(ctx, next) { const token = ctx.request.header.authorization; try { if (token) { // 验证当前token const decode = app.jwt.verify(token, options.secret); // 验证用户信息是否正常 if (!decode || !decode.account) { ctx.body = ctx.helper.result('', -1, '用户信息缺失', 1); } // 验证用户是否存在 const user = await ctx.model.Consumer.User.findOne( { where: { account: decode.account } } ); if (user) { // 如果口令有效期小于15分钟则发送新口令给前端 if (decode.exp - Date.now() / 1000 < 60 * 15) { const token = ctx.helper.generate_token(decode.account); ctx.set('Authorization', token); } // 当所有验证都通过之后,可以正常访问 await next(); } else { ctx.body = ctx.helper.result('', -1, '用户信息验证失败', 1); } } else { ctx.body = ctx.helper.result('', -1, '口令验证失败', 1); } } catch (e) { console.log(e); ctx.body = ctx.helper.result('', -1, e, 1); } }; };
2、原理解析
大家仔细看代码。在各种验证之后最后只有await next()部分才是最终正确代码执行下去的。代表权鉴没有问题。
从前端的角度来看,无非就是if else之后通过了。
3、口令刷新问题
上面的代码有一个部分
// 如果口令有效期小于15分钟则发送新口令给前端 if (decode.exp - Date.now() / 1000 < 60 * 15) { const token = ctx.helper.generate_token(decode.account); ctx.set('Authorization', token); }
这里比较关键,在于实现当用户不停操作之后能不会因为口令过期而突然退出登录。所以就需要校验口令的快过期时间。当快过期的时候发一个新的口令给前端。让前端刷新当前缓存的口令。注意不能时间太短。15分钟算一个比较合理的时间。
4、后端某些接口不校验口令直接过。
这个在后台管理的项目中基本不用太担心。但是开放式的博客就很有必要了。这里主要是看eggjs官方对于中间件的处理。
地址:https://eggjs.org/zh-cn/basics/middleware.html
我个人也对应进行配置
// 中间件配置 config.middleware = [ 'jwtAuthorize' ]; // 给jwtAuthorize中间件传入的参数 config.jwtAuthorize = { secret: 'daihaitian19940329', // 忽略指定路由 ignore: [ `${config.dreamCustom.prefix}/noauth` ], };
三、前端存储口令以及口令发送问题
1、登录接口
登录部分很简单,就是缓存用户信息就行了。主要核心在于ajax函数封装的地方
// 登录 login() { let data = { account: this.logindata.userName, password: userLoginPassword(this.logindata.password) }; this.logining = true; this.axios .ajax({ url: this.$api.consumer().user.login, data: data, method: "post" }) .then(e => { this.logining = false; // 存储用户数据到缓存 store.clearAll(); store.set("user_info", e.data); console.log(e.data); }) .catch(e => { this.logining = false; console.log(e); });
2、封装ajax函数,并且全局处理口令以及口令刷新的问题
代码放出核心封装部分
1、一个headers的封装。需要在前端有口令数据的情况将口令发送给后端
// 授权函数封装 const authorize = herders => { const user_info = store.get("user_info"); if (user_info && user_info.sign) { herders.Authorization = user_info.sign; return herders; } else { return herders; } };
2、口令刷新函数的封装
进行一系列的验证之后,如果后端返回的数据headers里面产生了口令需要及时刷新口令
// 刷新口令以及判断数据类型来判断是否退出登录 refresh_sign_or_out(res) { console.log(res); if (!res || !res.data) { this.logout(); return false; } // type类型为1必定退出登录 if (res.data.type === 1) { this.logout(); return false; } if (res.headers.authorization) { const user_info = store.get("user_info"); user_info.sign = res.headers.authorization; store.set("user_info", user_info); } return true; }
四、缺陷回顾
1、目前最大的缺陷是口令只要生成了,那么就可以验证通过。
2、如果解决上一个问题,让口令唯一的话,那么就需要解决并发情况下口令更新,保证后续接口不会因为口令刷新而报错
3、口令唯一化问题
个人:不处理了。目前这样就可以保证并发的问题,只要不口令唯一化,那么就不用担心上述问题。而且个人属于前端,不打算深入探究了。