Bearer Token 认证
要点
- Hono 内置
bearerAuth()中间件验证固定 token,适合 API Key 和简单场景 bearerAuth()支持单一 token 和验证函数两种模式- API Key 模式适合服务间调用,不需要用户上下文
- Bearer Token 和 JWT 的区别:前者是无结构字符串,后者是带签名的结构化令牌
- API Key 通常放在请求头或查询参数,bearer token 只能放在 Authorization 头
- 生产环境的 API Key 需要支持多 key、权限范围和失效
1. 基本用法
bearerAuth() 是 Hono 提供的简单 token 验证中间件,接收一个 token 字符串:
// src/index.ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
const app = new Hono()
const API_TOKEN = 'my-secret-token-12345'
app.use('/api/*', bearerAuth({ token: API_TOKEN }))
app.get('/api/data', (c) => c.json({ data: 'protected' }))
export default app请求:
curl -H "Authorization: Bearer my-secret-token-12345" \
http://localhost:8787/api/data如果请求不带 Authorization 头,或 token 不匹配,bearerAuth() 返回 401。
2. 自定义验证函数
如果需要从数据库查询 token 或支持多个 token,可以用 verifyToken 函数替代固定的 token 字符串:
// src/index.ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
const app = new Hono()
// 模拟数据库里的 token 列表
const validTokens = new Set([
'token-1',
'token-2',
'token-3',
])
app.use('/api/*', bearerAuth({
verifyToken: async (token, c) => {
return validTokens.has(token)
},
}))
app.get('/api/data', (c) => c.json({ data: 'protected' }))
export default appverifyToken 接收 token 字符串和 context,返回布尔值。返回 true 表示验证通过,false 表示拒绝。
这种方式适合:
- 从数据库或缓存查询 token 是否有效
- 支持多个 token(多客户端场景)
- 在验证的同时查询 token 对应的权限信息
3. API Key 模式
API Key 是 Bearer Token 的一种常见用法,用于服务间调用。与用户登录的 JWT 不同,API Key 代表的是一个应用或账号,而不是某个用户会话。
API Key 通常放在请求头:
// src/index.ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
import { createMiddleware } from 'hono/factory'
const app = new Hono()
type ApiKeyInfo = {
key: string
name: string
permissions: string[]
}
// 验证 API Key,并把信息存入 context
const apiKeyAuth = createMiddleware<{ Variables: { apiKey: ApiKeyInfo } }>(
async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return c.json({ error: 'Missing API key' }, 401)
}
// 查询 API Key(略)
const keyInfo: ApiKeyInfo = {
key: token,
name: 'Client App',
permissions: ['read:users', 'write:posts'],
}
c.set('apiKey', keyInfo)
await next()
}
)
app.use('/api/*', apiKeyAuth)
app.get('/api/users', (c) => {
const apiKey = c.get('apiKey')
if (!apiKey.permissions.includes('read:users')) {
return c.json({ error: 'Forbidden' }, 403)
}
return c.json([])
})
export default appAPI Key 也可以放在查询参数或自定义请求头里,有些 API 提供商支持多种位置:
// 从查询参数读取 API Key
const apiKey = c.req.query('api_key')
// 从自定义请求头读取
const apiKey = c.req.header('X-API-Key')如果项目需要兼容多种 API Key 传递方式,需要自己写中间件处理。bearerAuth() 只支持 Authorization 头。
4. Bearer Token vs JWT
两种认证方式的对比:
| 维度 | Bearer Token(固定) | JWT |
|---|---|---|
| 结构 | 无结构字符串 | header.payload.signature |
| 信息承载 | 不承载用户信息 | 携带 userId、role 等声明 |
| 验证方式 | 服务端查表比对 | 验证签名,不需要查表 |
| 过期控制 | 服务端管理 | token 自带 exp 字段 |
| 适用场景 | API Key、服务间调用 | 用户会话、无状态鉴权 |
简单场景(单客户端、不需要区分用户)用 bearerAuth() 足够。多用户、需要无状态扩展的场景用 JWT。
两者也可以组合使用:外部客户端用 API Key 认证身份,内部用户用 JWT 认证会话。
5. 多 API Key 与权限范围
生产环境的 API Key 通常有多个,每个 key 有不同的权限范围:
// src/middleware/api-key-auth.ts
import { createMiddleware } from 'hono/factory'
type ApiKeyRecord = {
id: string
name: string
key: string
permissions: string[]
rateLimit: number
}
// 模拟数据库
const apiKeys: ApiKeyRecord[] = [
{ id: '1', name: 'App A', key: 'key-aaa', permissions: ['read:users'], rateLimit: 100 },
{ id: '2', name: 'App B', key: 'key-bbb', permissions: ['read:users', 'write:posts'], rateLimit: 500 },
]
export const apiKeyAuth = createMiddleware<{
Variables: { apiKey: ApiKeyRecord }
}>(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
?? c.req.header('X-API-Key')
if (!token) {
return c.json({ error: 'Missing API key' }, 401)
}
const record = apiKeys.find((k) => k.key === token)
if (!record) {
return c.json({ error: 'Invalid API key' }, 401)
}
c.set('apiKey', record)
await next()
})
// 权限校验中间件
export function requirePermission(permission: string) {
return createMiddleware(async (c, next) => {
const apiKey = c.get('apiKey') as ApiKeyRecord
if (!apiKey.permissions.includes(permission)) {
return c.json({ error: 'Insufficient permissions' }, 403)
}
await next()
})
}// src/index.ts
import { Hono } from 'hono'
import { apiKeyAuth, requirePermission } from './middleware/api-key-auth'
const app = new Hono()
app.use('/api/*', apiKeyAuth)
app.get('/api/users', requirePermission('read:users'), (c) => {
return c.json([])
})
app.post('/api/posts', requirePermission('write:posts'), async (c) => {
return c.json({ id: 1 }, 201)
})
export default app6. API Key 的安全考虑
API Key 是长期有效的凭证,一旦泄露影响范围较大。几个安全措施:
6.1 前缀区分环境
不同环境的 API Key 使用不同前缀,便于识别和防止误用:
sk_live_abc123... ← 生产环境
sk_test_xyz789... ← 测试环境
sk_dev_foo456... ← 开发环境
中间件可以检查前缀,拒绝生产 key 在测试环境使用:
if (token.startsWith('sk_live_') && process.env.NODE_ENV !== 'production') {
return c.json({ error: 'Production key cannot be used in this environment' }, 403)
}6.2 限制 IP 来源
API Key 可以绑定允许使用的 IP 地址或 CIDR:
const record = apiKeys.find((k) => k.key === token)
if (record?.allowedIps) {
const clientIp = c.req.header('x-forwarded-for') ?? c.req.header('x-real-ip')
if (!record.allowedIps.includes(clientIp ?? '')) {
return c.json({ error: 'IP not allowed' }, 403)
}
}6.3 记录使用情况
每次 API Key 被使用时记录日志,便于审计和异常检测:
export const apiKeyAuth = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) return c.json({ error: 'Missing API key' }, 401)
const record = await db.apiKey.findUnique({ where: { key: token } })
if (!record) return c.json({ error: 'Invalid API key' }, 401)
// 记录使用日志
await db.apiKeyUsage.create({
data: {
apiKeyId: record.id,
method: c.req.method,
path: c.req.path,
ip: c.req.header('x-forwarded-for') ?? 'unknown',
timestamp: new Date(),
},
})
c.set('apiKey', record)
await next()
})6.4 支持失效
API Key 应该支持失效(用户主动吊销或系统检测到异常后自动吊销):
// 吊销 API Key
app.post('/api-keys/:id/revoke', async (c) => {
const id = c.req.param('id')
await db.apiKey.update({
where: { id },
data: { revokedAt: new Date() },
})
return c.json({ ok: true })
})
// 验证时检查吊销状态
const record = await db.apiKey.findUnique({ where: { key: token } })
if (!record || record.revokedAt) {
return c.json({ error: 'API key has been revoked' }, 401)
}7. 错误响应格式
bearerAuth() 默认的错误响应是纯文本。如果需要 JSON 格式,可以自定义 noTokenHandler 和 unauthorizedHandler:
app.use('/api/*', bearerAuth({
token: API_TOKEN,
noTokenHandler: (c) => {
return c.json({ error: 'Missing authentication token' }, 401)
},
unauthorizedHandler: (c) => {
return c.json({ error: 'Invalid or expired token' }, 401)
},
}))或者不用 bearerAuth(),自己写一个返回 JSON 的中间件(见第 3 节示例)。
总结
Bearer Token 认证是 Hono 提供的轻量级鉴权方案,适合 API Key、服务间调用等简单场景。
这一节涉及到的几个层次:
- 固定 token 验证:
bearerAuth({ token })最简单的用法 - 自定义验证函数:
verifyToken支持多 key、数据库查询 - API Key 模式:携带权限信息,适合多客户端场景
- 与 JWT 的区别:无结构字符串 vs 带签名的结构化令牌
- 安全措施:前缀区分环境、IP 限制、使用审计、支持失效
Bearer Token 认证适合「不需要用户上下文」的场景。如果需要区分用户、支持过期、携带声明,应该用 JWT。两者也可以在同一项目里并存——外部 API 用 API Key,内部用户用 JWT。
下一篇看请求 ID 中间件——requestId 的生成、透传和分布式追踪接入。