Egg.js为企业级框架和应用而生,是阿里开源的企业级 Node.js 框架,它可以轻松创建一个项目而不用做很多繁琐的初期工作,解放生产力,更可贵的是有一套现成的规范提供给我们,适合开发者较为快速的编写后端接口。本篇使用egg.js框架,辅以egg-jwt、cookie、redis、egg-swagger-ui以及egg-sequelize技术,实现了一种登录鉴权的方案。
登录页面中,用户输入用户名、密码进行登录操作,后台查询数据库中登录信息是否一致,若不一致,则用户名密码输入错误,直接返回登录页面;若一致,则判定登录成功,通过egg-jwt插件产生token,并将信息写入redis,同时将token放入cookie中,返回给浏览器。详细流程图如下所示。
当用户请求接口时,后台先获取传入的cookie值,若未携带cookie信息,则判定用户未登录,返回未登录状态,前端可通过该状态跳转至登录页面;若携带了cookie信息,则解密cookie值,获取到token的加密信息,通过egg-jwt密钥,解密token获取到用户登录名,鉴权中间件根据用户名以及token加密值查询redis并进行对比,若一致,则更新redis过期时间并继续进行接口请求;若不一致,则返回登录过期或异地登录。详细流程图如下所示。
代码实现
1、 egg-jwt及redis配置
config/config.default.ts中
config.jwt = {
secret: 'test123456', //自定义token的加密条件字符串,可按各自的需求填写
};
/** [必填]redis缓存配置 **/
config.redis = {
client: {
host: '127.0.0.1',
port: 6379,
password: '',
db: 0
},
};
在config/plugin.ts中
jwt: {
enable: true,
package: 'egg-jwt',
},
redis: {
enable: true,
package: 'egg-redis',
},
登录接口代码
public async login() {
const { ctx, app } = this;
let name = ctx.query.userName;
let pwd = ctx.query.pwd;
//查询数据库用户名密码是否匹配
let resultCount = await ctx.model.userModel.count({
where: {
name,
pwd
}
})
if (resultCount > 0) {
//jwt加密产生token
let token = app.jwt.sign({ name }, app.config.jwt.secret)
//name:token键值对存入redis并设置过期时间
let maxAge = 60 * 30;
await app.redis.set(name, token, 'EX', maxAge)
//给浏览器返回cookie
ctx.cookies.set("token", token, {
// 只允许服务端访问cookie
httpOnly: true,
// 对cookie进行签名,防止用户修改cookie
signed: true,
// 是否对cookie进行加密
encrypt: true
})
this.BaseResponse(0, 'success', '登录成功')
}else{
this.BaseResponse(0, 'error', '用户名密码错误')
}
}
退出接口代码
public async logout() {
const { ctx, app } = this
//解密请求头携带的cookie
let cookie = ctx.cookies.get('token', {
encrypt: true
})
//jwt根据密钥解密
let data: any = app.jwt.verify(cookie, app.config.jwt.secret)
let name = data.name
//清除redis中该name对应的键值对
await app.redis.del(name)
this.BaseResponse(0, '', '退出')
}
鉴权中间件代码
app/middleware/authLogin.ts
module.exports = (app) => {
return async function authLogin(ctx, next) {
let cookie = ctx.cookies.get('token', {
encrypt: true
})
if (cookie) {
let data = app.jwt.verify(cookie, app.config.jwt.secret)
let name = data.name
if (name) {
let res = await app.redis.get(name);
//redis存储的值和cookie中解密的值相同
if (res && res === cookie) {
//继续进行接口调用
await next();
//刷新redis过期时间
let maxAge = 60 * 30;
await app.redis.expire(name, maxAge)
} else if (res && res !== cookie) {
//接口拦截,返回未登录状态(被挤下线)
ctx.body = { code: '-1', msg: '该账号在其他PC登录' }
} else {
ctx.body = { code: '-1', msg: '已过期' }
}
} else {
//未登录
ctx.body = { code: '-1', msg: '未登录' }
}
} else {
//未登录
ctx.body = { code: '-1', msg: '未登录' }
}
};
}
接口鉴权拦截
app/router.ts中
const authLogin = app.middleware.authLogin(app);
router.get(baseUrl + '/ph/type/list',authLogin, controller.hyController.getAll)
效果
登录成功后携带cookie
Redis
退出后接口请求
登录后接口请求