Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准([(RFC 7519]).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登陆(SSO)场景。JWT的声明通常被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也能够增长一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。web
JWT认证流程npm
基于session和基于JWT的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而JWT是保存在客户端的。api
一旦签发一个JWT,在到期以前就会始终有效,没法中途废弃。安全
# jwt
npm i egg-jwt
# 密码加密
bcryptjs
# 数据校验
egg-validate
复制代码
config/plugin.jsbash
jwt: {
enable: true,
package: 'egg-jwt',
},
validate: {
enable: true,
package: 'egg-validate',
},
复制代码
config/config.default.js服务器
// jwt
config.jwt = {
secret: '123456',
expiresIn: '24h',
};
// 参数
config.validate = {
enable: true,
package: 'egg-validate',
};
// 密码加密
config.bcrypt = {
saltRounds: 10,
};
复制代码
app/model/user.jsmarkdown
'use strict';
module.exports = app => {
const { STRING, INTEGER, DECIMAL, DATE } = app.Sequelize;
const User = app.model.define('users', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
userName: {
type: STRING,
allowNull: false,
unique: true,
comment: '用户名,惟一',
},
passWord: STRING,
});
return User;
};
复制代码
app/controller/user.js网络
// 校验用户注册参数
const vUser = {
userName: { type: 'string', required: true },
passWord: { type: 'string', required: true },
};
//...
class UserController extends Controller {
// 用户注册
async rigist() {
const { ctx } = this;
// 接收并校验参数
ctx.validate(vUser, ctx.request.body);
// 判断用户名是否重复
const users = await ctx.service.user.checkUserName(ctx.request.body);
if (users[0]) {
ctx.body = { status: false, msg: '用户名已存在', data: users };
return;
}
await ctx.service.user.Rigist(ctx.request.body);
ctx.body = { status: true, msg: '注册成功' };
}
// 用户登录
async login() {
const { ctx } = this;
// 接收并校验参数
ctx.validate(vUser, ctx.request.body);
const data = await ctx.service.user.Login(ctx.request.body);
if (!data) {
ctx.status = 401;
ctx.body = { status: false, msg: '用户名或密码错误' };
return;
}
ctx.body = { status: true, msg: '登录成功', data };
}
}
module.exports = UserController;
复制代码
app/service/user.jssession
const bcrypt = require('bcryptjs');
// ......
// 检查用户名
async checkUserName(query) {
const { userName } = query;
const users = await this.ctx.model.User.findAll({
attributes: [ 'userName' ],
where: { userName },
});
return users;
}
// 用户注册
async Rigist(body) {
const { userName, passWord } = body;
// 对密码加密
const hash = bcrypt.hashSync(passWord, this.config.bcrypt.saltRounds);
const user = await this.ctx.model.User.create({ userName, passWord: hash });
return user;
}
// 用户登录
async Login(body) {
const { userName, passWord } = body;
const user = await this.ctx.model.User.findOne({
where: { userName },
});
if (!user) return {};
const match = await bcrypt.compare(passWord, user.passWord);
if (match) {
const { id, userName } = user;
// 获取jwt配置
const { jwt: { secret, expiresIn } } = this.app.config;
// 生成token
const token = this.app.jwt.sign({
id, userName,
}, secret, { expiresIn });
return { userName, token };
}
}
复制代码
app/router.jsapp
router.post('/api/v1/user/rigist', controller.user.rigist); // 用户注册
router.post('/api/v1/user/login', controller.user.login); // 用户登录
// 在路由里添加jwt中间件 便可使用jwt鉴权
router.put('/api/v1/user/:id', app.jwt, controller.user.update); // 修改用户信息
复制代码
若是接口使用了app.jwt 中间件
const userToken = this.ctx.state.user;
// 可获取登录时写入token的内容 id, userName,
复制代码
若是接口未使用app.jwt中间件 则没法获取 this.ctx.state.user;
对与某些接口可登录也可不登录,且获取数据有差别时。
此时须要封装解析token的方法
app/extend/utils.js
'use strict';
function getTokenInfo(jwt, auth, secret) {
// 判断请求头是否包含token
if (
auth.authorization &&
auth.authorization.split(' ')[0] === 'Bearer'
) {
const token = auth.authorization.split(' ')[1];
let decode = '';
if (token) {
decode = jwt.verify(token, secret);
}
return decode;
}
return;
}
module.exports = {
getTokenInfo,
};
复制代码
在service里使用
const { jwt: { secret } } = this.app.config;
// 若是存在token 解析token
const authInfo = getTokenInfo(this.ctx.app.jwt, this.ctx.headers, secret);
if (authInfo) {
// 便可获取 token 内容
// authInfo.id authInfo.userName
}
复制代码