koa-jwt 使用文档
概述
koa-jwt 是一个用于在 Koa.js 应用中通过 JSON Web Tokens (JWT) 进行 HTTP 请求认证的中间件。
版本要求
| Koa 版本 | Node 版本 | 应使用的 koa-jwt 版本 |
|---|---|---|
| koa 2+ | < 7.6 | koa-jwt@2 |
| koa 2+ | ≥ 7.6 | koa-jwt@3+(master 分支) |
| koa 1 | 任意 | koa-jwt@1 |
安装
npm install koa-jwt
基本用法
基础配置示例
const Koa = require('koa');
const jwt = require('koa-jwt');
const app = new Koa();
// 自定义 401 错误处理
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (err.status === 401) {
ctx.status = 401;
ctx.body = 'Protected resource, use Authorization header to get access\n';
} else {
throw err;
}
}
});
// 公共路由(无需认证)
app.use(async (ctx, next) => {
if (ctx.url.match(/^\/public/)) {
ctx.body = 'unprotected\n';
} else {
await next();
}
});
// JWT 认证中间件(从此以下的中间件需要有效 JWT)
app.use(jwt({ secret: 'shared-secret' }));
// 受保护的路由
app.use(async (ctx) => {
if (ctx.url.match(/^\/api/)) {
ctx.body = 'protected\n';
}
});
app.listen(3000);
使用 unless 排除特定路径
const Koa = require('koa');
const jwt = require('koa-jwt');
const app = new Koa();
// 使用 unless 排除特定路径
app.use(jwt({ secret: 'shared-secret' }).unless({ path: [/^\/public/] }));
app.listen(3000);
配置选项
主要配置项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
secret | String/Array/Function/Buffer | 必填 | 加密密钥(支持数组、函数、公钥) |
key | String | ‘user’ | 存储解码数据的 ctx.state 属性名 |
getToken | Function | undefined | 自定义获取 token 的函数 |
isRevoked | Function | undefined | 检查 token 是否被吊销的函数 |
passthrough | Boolean | false | 是否始终调用 next() |
cookie | String | undefined | 从 cookie 获取 token 的字段名 |
audience | String | undefined | 验证受众 |
issuer | String | undefined | 验证签发者 |
debug | Boolean | false | 是否开启调试模式 |
tokenKey | String | undefined | 存储原始 token 的 ctx.state 属性名 |
Token 获取顺序
opts.getToken函数(如果提供)- Cookie(如果
opts.cookie设置) - Authorization 头部(Bearer token)
高级功能
自定义 Token 获取函数
app.use(jwt({
secret: 'shared-secret',
getToken: function(opts) {
// 自定义获取 token 的逻辑
// this 指向 ctx 对象
return this.get('x-custom-token');
}
}));
getToken 函数接口
/**
* 自定义 token 解析器
* @this 传递给中间件的 ctx 对象
* @param {Object} opts 中间件的选项
* @return {String|null} 解析到的 token 或 null(如果未找到)
*/
检查 Token 是否吊销
app.use(jwt({
secret: 'shared-secret',
isRevoked: async (ctx, decodedToken, token) => {
// 检查 token 是否被吊销
const isRevoked = await checkTokenRevocation(token);
// 如果 token 未被吊销,必须返回 false,否则返回 true
return isRevoked;
}
}));
isRevoked 函数接口
/**
* 自定义 isRevoked 解析器
* @param {object} ctx 传递给中间件的 ctx 对象
* @param {object} decodedToken Token 的内容
* @param {object} token token 字符串
* @return {Promise} 如果 token 未被吊销,promise 必须 resolve false,
* 否则(promise resolve true 或出错)token 被视为吊销
*/
使用多个密钥
// 支持密钥轮换
app.use(jwt({
secret: ['old-shared-secret', 'new-shared-secret']
}));
使用公钥/私钥
const fs = require('fs');
const publicKey = fs.readFileSync('/path/to/public.pub');
app.use(jwt({ secret: publicKey }));
动态密钥函数
app.use(jwt({
secret: (header, payload) => {
// 根据 header 和 payload 返回密钥
// 例如,根据 key id (kid) 获取对应的密钥
return Promise.resolve(getSecretBasedOnKid(header.kid));
}
}));
使用 JWKS(JSON Web Key Set)
const { koaJwtSecret } = require('jwks-rsa');
app.use(jwt({
secret: koaJwtSecret({
jwksUri: 'https://your-domain/.well-known/jwks.json',
cache: true,
cacheMaxEntries: 5,
cacheMaxAge: 600000 // 10分钟
}),
audience: 'http://myapi/protected',
issuer: 'http://your-issuer'
}));
数据访问
默认访问方式
认证成功后,解码的数据存储在 ctx.state.user:
app.use(jwt({ secret: 'shared-secret' }));
app.use(async (ctx) => {
console.log(ctx.state.user); // JWT payload 数据
ctx.body = `Hello, ${ctx.state.user.username}`;
});
自定义存储位置
app.use(jwt({
secret: 'shared-secret',
key: 'jwtdata'
}));
app.use(async (ctx) => {
console.log(ctx.state.jwtdata); // 使用自定义键名
});
访问原始 Token
app.use(jwt({
secret: 'shared-secret',
tokenKey: 'rawToken'
}));
app.use(async (ctx) => {
console.log(ctx.state.rawToken); // 原始 token 字符串
});
Passthrough 模式
基础用法
app.use(jwt({
secret: 'shared-secret',
passthrough: true
}));
app.use(async (ctx) => {
if (ctx.state.user) {
// 已认证用户
ctx.body = 'Authenticated content';
} else {
// 未认证用户
ctx.body = 'Public content';
}
});
访问错误信息
app.use(jwt({
secret: 'shared-secret',
passthrough: true
}));
app.use(async (ctx) => {
if (ctx.state.jwtOriginalError) {
// 有 token 但验证失败
console.log('JWT Error:', ctx.state.jwtOriginalError.message);
}
});
错误处理
自定义错误处理
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (err.status === 401) {
ctx.status = 401;
ctx.body = {
error: err.originalError ? err.originalError.message : err.message
};
} else {
throw err;
}
}
});
Token 验证错误代码
所有 token 验证错误代码可以在 jsonwebtoken 文档 中找到。
常见错误:
TokenExpiredError:token 已过期JsonWebTokenError:token 无效NotBeforeError:token 尚未生效
密钥提供方式
方式 1:直接配置
app.use(jwt({ secret: 'static-secret' }));
方式 2:每请求动态密钥
// 通过中间件设置每请求密钥
app.use(async (ctx, next) => {
ctx.state.secret = getSecretForRequest(ctx);
await next();
});
// 使用默认密钥(当 ctx.state.secret 不存在时使用)
app.use(jwt({ secret: 'default-secret' }));
完整示例
示例 1:完整应用
const Koa = require('koa');
const jwt = require('koa-jwt');
const app = new Koa();
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (err.status === 401) {
ctx.status = 401;
ctx.body = {
code: 'UNAUTHORIZED',
message: 'Authentication required',
details: err.originalError ? err.originalError.message : null
};
} else {
ctx.status = err.status || 500;
ctx.body = {
code: 'INTERNAL_ERROR',
message: err.message
};
}
}
});
// 公共中间件
app.use(async (ctx, next) => {
// 记录请求日志
console.log(`${ctx.method} ${ctx.url}`);
await next();
});
// JWT 认证(排除公开路径)
app.use(jwt({
secret: process.env.JWT_SECRET || 'default-secret',
audience: 'my-api',
issuer: 'my-auth-service'
}).unless({
path: [
/^\/public/,
/^\/login/,
/^\/health/
]
}));
// 受保护的路由
app.use(async (ctx) => {
if (ctx.path === '/api/profile') {
ctx.body = {
user: ctx.state.user,
message: 'This is your profile'
};
} else if (ctx.path === '/api/data') {
ctx.body = {
data: 'Sensitive information',
accessTime: new Date().toISOString()
};
}
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
示例 2:使用环境变量和高级配置
const Koa = require('koa');
const jwt = require('koa-jwt');
const { koaJwtSecret } = require('jwks-rsa');
const app = new Koa();
// 根据环境选择配置
const jwtConfig = {
secret: process.env.JWT_SECRET_TYPE === 'jwks'
? koaJwtSecret({
jwksUri: process.env.JWKS_URI,
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5
})
: process.env.JWT_SECRET,
audience: process.env.JWT_AUDIENCE,
issuer: process.env.JWT_ISSUER,
// 从 cookie 或 header 获取 token
cookie: 'auth_token',
getToken: function(opts) {
// 优先从 header 获取
const headerToken = this.get('authorization');
if (headerToken && headerToken.startsWith('Bearer ')) {
return headerToken.substring(7);
}
// 其次从 cookie 获取
if (opts.cookie && this.cookies.get(opts.cookie)) {
return this.cookies.get(opts.cookie);
}
return null;
}
};
app.use(jwt(jwtConfig).unless({
path: [
/^\/docs/,
/^\/swagger/,
{ url: '/', methods: ['GET'] }
]
}));
// 业务逻辑
app.use(async (ctx) => {
ctx.body = {
success: true,
user: ctx.state.user,
timestamp: new Date().toISOString()
};
});
module.exports = app;
注意事项
-
安全性
- 不要将密钥硬编码在代码中
- 使用环境变量或安全的配置管理系统
- 定期轮换密钥
-
性能
- 对于高频请求,考虑缓存公钥或 JWKS 响应
- 使用适当的缓存策略减少网络开销
-
版本兼容性
- koa-jwt v3+ 不再从 jsonwebtoken 导出
sign、verify和decode函数 - 如需这些功能,直接安装
jsonwebtoken包
- koa-jwt v3+ 不再从 jsonwebtoken 导出
-
调试
- 设置
debug: true可以在控制台看到详细的验证信息 - 生产环境应关闭调试模式
- 设置
340



被折叠的 条评论
为什么被折叠?



