课程名称:web前端架构师
课程章节:第16周 第五章 JWT 完成用户认证
主讲老师:张轩
课程内容: JWT 简介及优缺点、使用 JWT 完成用户认证
JWT 简介
JWT 官网 https://jwt.io/
基本工作流程
用户登录时,服务端生成 token 并返回给客户的,客户的保存起来,之后请求携带 token 进行访问,服务端会对用户携带的 token 进行验证
JWT token 的组成
Header:JSON对象,描述 JWT 的元数据,加密算法以及类型
Payload:JSON对象,存放数据需要传递的数据
Signature:对前两部分的签名,防止数据篡改。需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。
Token 优点
- token 是无状态的(stateless),服务器不需要记录任何信息,不占用内存
- 多进程,多服务器集群没有影响,易于扩展
- 假如不记录在 cookie 中,没有跨域的影响
- 和服务器端解耦,任何设备都可以生成token。
Token 的缺点
无法废弃,没有办法对快速对已经登录的用户做处理。
空间更大,所有数据是通过 base64进行编码的,会随着数据量的增大而变大。
使用 JWT 完成用户认证
使用 jsonwebtoken
npm i jsonwebtoken
npm i @types/jsonwebtoken -D
生成 token
import jwt from 'jsonwebtoken'
const token = jwt.sign({ foo: 'bar' }, 'shhhhh');
验证 token
jwt.verify(token, 'shhhhh')
添加生成 token 方法
public sign(data: any) {
return jwt.sign(data, this.app.config.keys, {
// algorithm: 'HS384',
expiresIn: 60 * 60,
});
}
返回给登录用户
ctx.helper.success({
ctx,
res: {
...user.toJSON(),
token: this.service.user.sign({ username: user.username }),
},
msg: '登录成功',
});
添加验证方法。客户的发送的请求头中会有 Authorization
这个字段,它的只就是 Bearer + 空格 + token
public validateToken() {
const { authorization } = this.ctx.request.headers;
if (authorization && typeof authorization === 'string') {
const token = authorization.trim().split(' ')[1];
if (token) {
try {
return jwt.verify(token, this.app.config.keys);
} catch (error) {
return false;
}
}
}
return false;
}
返回给登录用户
public async getUser() {
const { ctx } = this;
const user = ctx.service.user.validateToken();
if (user) {
ctx.helper.success({
ctx,
res: user,
msg: '获取用户信息成功',
});
} else {
ctx.helper.error({
ctx,
errType: 'signValidateFail',
});
}
}
使用 JWT 中间键完成用户认证
用户验证的逻辑肯定在很多地方会用到,所以需要将验证逻辑封装成一个中间键
// app/middleware/jwt.js
import jwt from 'jsonwebtoken';
import { Context } from 'egg';
import { Application } from 'egg';
// 获取并验证 token
function getToken(ctx: Context, app: Application) {
const { authorization } = ctx.request.headers;
if (authorization && typeof authorization === 'string') {
const token = authorization.trim().split(' ')[1];
if (token) {
try {
return jwt.verify(token, app.config.keys);
} catch (error) {
return false;
}
}
}
return false;
}
export default function (_options, app: Application) {
return async (ctx: Context, next: () => Promise<any>) => {
const decode = getToken(ctx, app);
if (!decode) {
return ctx.helper.error({
errType: 'signValidateFail',
ctx,
});
}
// 将验证解密信息保存到 state 中
ctx.state.user = decode;
await next();
};
}
在我们需要验证的接口加上该中间键
import { Application } from 'egg';
export default (app: Application) => {
const { controller, router } = app;
const jwt = app.middleware.jwt({}, app);
router.get('/', controller.home.index);
router.get('/user', jwt, controller.user.getUser);
};
在 controller 中根据验证信息获取用户信息
...
public async getUser() {
const { ctx, service } = this;
// 根据解密的信息获取用户信息
const user = await service.user.findByUsername(ctx.state.user.username);
ctx.helper.success({
ctx,
res: user,
msg: '获取用户信息成功',
});
}
...