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 app

verifyToken 接收 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 app

API 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 app

6. 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 格式,可以自定义 noTokenHandlerunauthorizedHandler

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、服务间调用等简单场景。

这一节涉及到的几个层次:

  1. 固定 token 验证bearerAuth(&#123; token &#125;) 最简单的用法
  2. 自定义验证函数verifyToken 支持多 key、数据库查询
  3. API Key 模式:携带权限信息,适合多客户端场景
  4. 与 JWT 的区别:无结构字符串 vs 带签名的结构化令牌
  5. 安全措施:前缀区分环境、IP 限制、使用审计、支持失效

Bearer Token 认证适合「不需要用户上下文」的场景。如果需要区分用户、支持过期、携带声明,应该用 JWT。两者也可以在同一项目里并存——外部 API 用 API Key,内部用户用 JWT。

下一篇看请求 ID 中间件——requestId 的生成、透传和分布式追踪接入。