Egg.js配置登录验证中间件
一、 安装jwt
npm install egg-jwt
二、 安装完成后在config --》plugin.js文件中开启jwt插件
"use strict";
module.exports = {
jwt: {
enable: true,
package: "egg-jwt",
},
}
三、 在app文件夹下创建middleware文件夹,创建verifyToken.js(命名随意)
module.exports = (options, app) => {
return async function jwtMiddleware(ctx, next) {
// 获取请求头中的 Authorization 字段,通常包含 JWT Token
const token = ctx.request.header.authorization;
// 不用token的列表
const whiteList = [
'/login',
'/prod-api/login',
];
// 跳过swagger的路径验证
if (ctx.path.indexOf('swagger') > -1 || ctx.path.indexOf('favicon') > -1) {
await next();
return;
}
app.logger.info(ctx.path);
// 如果没有提供 Token,返回 401 错误
if (!token && whiteList.indexOf(ctx.path) === -1) {
ctx.status = 200;
ctx.body = { code: 401, msg: '会话过期,请重新登录' };
return;
}
if (token) {
try {
// 验证 JWT Token
// 将解码后的用户信息挂载到 ctx.state.user 中,以便后续路由使用
ctx.state.userInfo = app.jwt.verify(
token.split('Bearer ')[1],
app.config.jwt.secret
);
} catch (err) {
// Token 验证失败,返回 401 错误
ctx.status = 200;
ctx.body = { code: 401, msg: '会话过期,请重新登录' };
return;
}
}
// 继续执行下一个中间件或路由处理函数
await next();
};
};
四、 同时要在config --》config.default.js中添加jwt配置
// 这里添加中间件(内容就是middleware目录下的中间件文件名)
config.middleware = ['verifyToken']
// jwt配置
config.jwt = {
secret: '项目token密钥随意填写',
sign: {
// jwt.sign(***,***,[options,***])方法中,options的默认设置可以在这里配置;
// 过期时间24小时
expiresIn: 24 * (60 * 60), // 多少s后过期。actionToken.js中,jwt.sing(plyload,secret,{expiresIn:number})会被合并,调用时设置优先级更高;
},
};
配置已经添加好了接下来就要写接口了。
五、 首先在router.js中添加登录路由
import { Application } from 'egg'
export default (app) => {
const { controller, router } = app
// 统一前缀方法可根据需求添加登录
router.prefix('/api')
// 添加登录控制器
router.post('/login', controller.sysBasic.login)
}
六、 创建login控制器
1. 需要在app --》controller文件夹下新建sysBasic.js 控制器文件(文件命名需同路由配置一样)
const Controller = require('egg').Controller;
class SysBasicController extends Controller {
// 登录
async login(ctx) {
ctx.body = await ctx.service.system.sysBasic.login(ctx.request.body);
ctx.status = 200;
}
// 获取验证码
async captchaImage(ctx) {
ctx.body = await ctx.service.system.sysBasic.captchaImage(ctx.request.body);
ctx.status = 200;
}
// 获取用户信息
async getInfo(ctx) {
ctx.body = await ctx.service.system.sysBasic.getInfo();
ctx.status = 200;
}
// 获取路由信息
async getRouters(ctx) {
ctx.body = await ctx.service.system.sysBasic.getRoute();
ctx.status = 200;
}
// 更新用户密码
async updatePwd(ctx) {
await ctx.service.system.sysBasic.updatePwd(ctx.query);
ctx.status = 200;
}
}
module.exports = SysBasicController;
控制器写好后我们需要添加对应的服务(Service)
/*
* @FilePath: /app/service/system/sysBasic.js
* @Description:
* 基础服务,如获取验证码等
*/
const Service = require('egg').Service;
// 验证码插件
const captchapng = require('captchapng');
// 加密模块
const utility = require('utility');
const { v4: uuidv4 } = require('uuid');
// 数据库模块
const { Op } = require('sequelize');
class SysBasicService extends Service {
constructor(ctx) {
super(ctx);
this.session = ctx.session;
this.getUserData = ctx.getUserData;
this.ResponseCode = ctx.response.ResponseCode;
this.ServerResponse = ctx.response.ServerResponse;
this.objUnderlineToHump = ctx.objUnderlineToHump;
this.objHumpToUnderline = ctx.objHumpToUnderline;
this.capitalizeFirstLetter = ctx.capitalizeFirstLetter;
this.changeToTree = ctx.changeToTree;
this.toInt = ctx.toInt;
this.SysUser = ctx.model.SysUser;
this.SysDept = ctx.model.SysDept;
this.SysRole = ctx.model.SysRole;
this.SysMenu = ctx.model.SysMenu;
this.SysUserRole = ctx.model.SysUserRole;
this.SysRoleMenu = ctx.model.SysRoleMenu;
}
/**
* 创建验证码
* @return
*/
async captchaImage() {
// 第三方插件,
const captchaVal = parseInt(Math.random() * 9000 + 1000);
const p = new captchapng(80, 30, captchaVal); // width,height,numeric captcha
p.color(205, 205, 205, 128); // First color: background (red, green, blue, alpha)
p.color(2, 5, 230, 255); // Second color: paint (red, green, blue, alpha)
const img = p.getBase64();
// 验证码存session
this.session.code = {
value: utility.md5(utility.md5(captchaVal + '')),
time: Date.now(),
};
return {
// TODO这里可以设置开启或者关闭系统的验证码,需要存在数据库中,在系统缓存中实现
captchaEnabled: true,
img,
msg: '操作成功',
uuid: uuidv4(),
code: 200,
};
}
/**
* 登录
* @param params
* @return
*/
async login(params) {
console.log('加密后的密码', utility.md5(params.password));
if (utility.md5(utility.md5(params.code)) !== this.session.code.value) {
return {
code: 600,
msg: '验证码不正确,请输入正确验证码',
};
}
const res = await this.SysUser.findOne({
where: {
user_name: params.username,
password: utility.md5(params.password),
},
raw: true,
});
if (!res) {
return this.ServerResponse.createByErrorCodeMsgBusiness(50001);
}
const token = this.app.jwt.sign(
{
userId: res.user_id,
userName: params.username,
},
this.app.config.jwt.secret
);
this.ctx.handleUser = {
userId: res.user_id,
userName: params.username,
};
return {
code: 200,
msg: '操作成功',
token,
};
}
/**
* 更新密码
* @param {*} params
*/
async updatePwd(params) {
const userData = await this.getUserData();
console.log(userData);
const res = await this.SysUser.findOne({
where: {
user_name: userData.userName,
password: utility.md5(params.oldPassword),
},
raw: true,
});
if (res) {
const findRepeat = await this.SysUser.findByPk(userData.userId);
await findRepeat.update({
user_id: userData.userId,
password: utility.md5(params.newPassword),
});
return this.ServerResponse.createBySuccessMsg('修改成功');
}
throw '原始密码不正确';
}
/**
* 获取登录信息
*/
async getInfo() {
const userResuserData = await this.getUserData();
const { userId } = userResuserData;
const userRes = await this.SysUser.findAll({
where: {
user_id: userId,
},
include: [{ model: this.SysDept }, { model: this.SysRole }],
});
const permissions = [];
const roles = [];
// 判断是否为超管权限
if (
userRes[0].sys_roles[0] &&
userRes[0].sys_roles[0].role_key === 'admin'
) {
permissions.push('*:*:*');
} else {
userRes[0].sys_roles.forEach(element => {
permissions.push(element.role_key);
});
}
userRes[0].sys_roles.forEach(element => {
roles.push(element.role_key);
});
// 返回成若依的数据结构
userRes[0].dataValues.roles = this.objUnderlineToHump(
JSON.parse(JSON.stringify(userRes[0].sys_roles))
);
userRes[0].dataValues.dept = this.objUnderlineToHump(
JSON.parse(JSON.stringify(userRes[0].sys_dept))
);
delete userRes[0].dataValues.sys_roles;
delete userRes[0].dataValues.sys_dept;
return {
code: 200,
msg: '操作成功',
permissions,
user: this.objUnderlineToHump(userRes[0].dataValues),
roles,
};
}
/**
* 获取用户的路由权限
* 路由返回参数及格式
*
* @param {*} params
*/
async getRoute() {
const userResuserData = await this.getUserData();
const { userName, userId } = userResuserData;
let res;
if (userName === 'admin') {
// 超管查询所有的菜单
res = await this.SysMenu.findAll({
where: {
status: '0',
[Op.or]: [
{
menu_type: 'M',
},
{
menu_type: 'C',
},
],
},
order: [
[ 'parent_id', 'asc' ],
[ 'order_num', 'asc' ],
],
});
} else {
// 查询 user对应的roleID
const rowIds = await this.SysUserRole.findAll({
where: {
user_id: userId,
},
});
const rowArr = [];
rowIds.forEach(element => {
rowArr.push(element.dataValues.role_id);
});
// 查询所有的匹配权限的菜单
res = await this.SysRoleMenu.findAll({
where: {
role_id: {
[Op.or]: rowArr,
},
},
include: [
{
model: this.SysMenu,
},
],
});
// console.log(res);
res = res.map(item => {
return item.sys_menu;
});
}
res = res.map(item => {
if (item) {
item = this.objUnderlineToHump(item.dataValues);
// 当为根路径并且不为外链时path加上’/‘
item.name = this.capitalizeFirstLetter(item.path);
if (item.parentId === 0 && item.isFrame) {
item.path = '/' + item.path;
}
return item;
}
});
res = res.filter(
item => item && (item.menuType === 'M' || item.menuType === 'C')
);
res = this.changeReturnTree(this.changeToTree(res, 'parentId', 'menuId'));
return this.ServerResponse.createBySuccessMsgAndData('查询成功', res);
}
/**
* 将路由树的数据库数据转换为对应的路由数据
* @param {*} data
*/
changeReturnTree(data) {
data = data.map(item => {
if (item.children) {
item.children = this.changeReturnTree(item.children);
}
return this.getRouteLint(item);
});
return data;
}
getRouteLint(item) {
let component = item.menuType === 'M' ? 'Layout' : item.component;
if (item.parentId !== 0 && component === 'Layout') {
component = 'ParentView';
}
const returnData = {
// 路由的名称
name: item.name,
// 路径的地址
path: item.path,
// 菜单是否隐藏
hidden: item.visible !== '0',
// 面包屑是否能点击 暂定菜单层次的才可以点击
// redirect: item.menuType == "C" ? "Redirect" : "noRedirect",
// 组件地址
component,
// 是否长展示
// alwaysShow: item.menuType == "M" ? "" : "noRedirect",
// 其他数据
meta: {
// 菜单名称
title: item.menuName,
// 图标
icon: item.icon,
// 是否缓存
noCache: !item.isCache,
// 跳转路径
link: !item.isFrame ? item.path : null,
},
};
if (item.children) {
returnData.children = item.children;
}
return returnData;
}
}
module.exports = SysBasicService;
还有关联的相对应的model文件这里可以自行创建了或者使用
egg-sequelize-auto 插件进行自动生成
到这里就可以使用jwt 验证前端传过来的authorization(token)字段。