废话部分
上次废话太多,估计有些小伙伴没看太清楚。这次会精简很多。只拿关键的出来讲。
个人属于前端,后端看到了扣下留情。希望本文能帮助大家
项目地址:
前端: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、口令唯一化问题
个人:不处理了。目前这样就可以保证并发的问题,只要不口令唯一化,那么就不用担心上述问题。而且个人属于前端,不打算深入探究了。