JWT
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。
部分容器/场景无法使用 cookie
,JWT 就是一个不错的选择。
基础配置
## 安装依赖
yarn add egg-jwt
config/config.default.ts
import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';
export default (appInfo: EggAppInfo) => {
const config = {} as PowerPartial<EggAppConfig>;
// override config from framework / plugin
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1589209631587_7631';
// add your egg config in here
// 统一处理错误
config.middleware = ['errorHandler'];
// jwt
// 参数参考:https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback
config.jwt = {
secret: '1234567890', // secretOrPublicKey
};
// add your special config in here
const bizConfig = {
// sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,
};
// the return config will combines to EggAppConfig
return {
...config,
...bizConfig,
};
};
config/plugin.ts
import { EggPlugin } from 'egg';
const plugin: EggPlugin = {
// static: true,
// nunjucks: {
// enable: true,
// package: 'egg-view-nunjucks',
// },
// JWT
jwt: {
enable: true,
package: 'egg-jwt',
},
};
export default plugin;
app/middleware/jwt.ts
import * as koajwt from 'koa-jwt2';
// 文档上这样使用,但是中间件类型不匹配
// app.get("/", app.jwt, "render.index");
module.exports = (options) => {
return koajwt(options);
};
app/middleware/error_handler.ts
import { Context } from 'egg';
module.exports = () => {
return async function errorHandler(ctx: Context, next) {
try {
await next();
} catch (err) {
// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
ctx.app.emit('error', err, ctx);
const status = err.status || 500;
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
const error =
status === 500 && ctx.app.config.env === 'prod' ? 'Internal Server Error' : err.message;
// 从 error 对象上读出各个属性,设置到响应中
ctx.body = ctx.helper.APIFail(error, status);
if (status === 422) {
ctx.body.detail = err.errors;
}
ctx.status = status;
}
};
};
使用
路由配置
import { Application } from 'egg';
/**
* 构建路径
* @param p 路径
*/
const buildPath = (p: string) => {
return `/api/app/${p}`;
};
export default (app: Application) => {
const { controller, router } = app;
// jwt 中间件,把请求头部带的 Authorization 转换成具体的数据
const jwt = app.middleware.jwt(app.config.jwt);
router.post(buildPath('user/login'), controller.app.user.PostLogin);
// 退出登录、清除 token
// https://github.com/okoala/koa-jwt2/blob/master/lib/index.js#L124
// isRevokedAsync 函数默认返回 options.isRevoked
// 清除 token 会抛出 revoked_token 错误
// https://github.com/okoala/koa-jwt2/blob/master/lib/errors/UnauthorizedError.js
router.get(
buildPath('user/logout'),
app.middleware.jwt({
...app.config.jwt,
isRevoked: true,
})
);
router.post(buildPath('micro-blog'), jwt, controller.app.user.PostMicroBlog);
};
控制器中使用
import { Controller } from 'egg';
export default class UserController extends Controller {
/**
* 用户登录;
* {string} user_name 用户名
* {string} password 密码
*/
public async PostLogin() {
const { ctx, app } = this;
const { body } = ctx.request;
// 用户的登录业务
// 登录成功
// 生成 token
const token = app.jwt.sign(
{
id: user.id,
user_name: user.user_name,
},
app.config.jwt.secret,
{
expiresIn: '24h',
},
);
ctx.body = ctx.helper.APISuccess({
token,
});
}
// 请求头注意,Authorization: Bearer xxxxxx
// 注意需要在上面生成的 token 上添加 `Bearer `,完整的数据是 `Bearer ${token}`
// 部分场景访问接口可能没有 token,koa-jwt2 会在应用内抛出一个错误,需要一个统一处理错误的中间件
public async PostMicroBlog() {
const { ctx } = this;
// ctx.state.user 上会有对应的数据
ctx.body = ctx.helper.APISuccess(ctx.state.user);
}
}