Bearer Token 认证

要点

  • Bearer Token 是 HTTP 认证方案,Token 通过 Authorization: Bearer <token> 传递
  • Bearer 意味着「持有者」——谁持有 Token 谁就有权限,不需要额外证明
  • JWT、OAuth Access Token、API Key 都可以用 Bearer 方案传递
  • Bearer Token 必须通过 HTTPS 传输,防止中间人窃取
  • Token 的撤销和刷新是 Bearer 方案的主要挑战
  • AI 项目的 API 服务通常使用 Bearer JWT 或 Bearer API Key

1. Bearer 认证原理

1.1 HTTP Authorization Header

Bearer Token 通过 HTTP Authorization Header 传递:

GET /api/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  • Bearer:认证方案(scheme)
  • 后面是 Token 字符串

1.2 与其他方案的对比

方案用途格式
Bearer通用 TokenBearer <token>
Basic用户名密码Basic base64(user:pass)
Digest用户名密码(更安全)Digest ...
TokenKubernetes 等Token <token>

Bearer 是最通用的方案,JWT、OAuth、API Key 都用它。

2. Hono 实现

2.1 基本实现

import { Hono } from 'hono'
import { verify } from 'hono/jwt'
 
const app = new Hono()
 
// Bearer Token 认证中间件
app.use('/api/*', async (c, next) => {
  const authHeader = c.req.header('Authorization')
 
  if (!authHeader) {
    return c.json({ error: 'Missing Authorization header' }, 401)
  }
 
  // 解析 Bearer Token
  const parts = authHeader.split(' ')
  if (parts.length !== 2 || parts[0] !== 'Bearer') {
    return c.json({ error: 'Invalid Authorization header format' }, 401)
  }
 
  const token = parts[1]
 
  try {
    // 验证 JWT
    const payload = await verify(token, process.env.JWT_SECRET!)
 
    // 设置上下文
    c.set('user', payload)
 
    await next()
  } catch (error) {
    return c.json({ error: 'Invalid token' }, 401)
  }
})
 
// 受保护的路由
app.get('/api/profile', (c) => {
  const user = c.get('user')
  return c.json(user)
})

2.2 使用 Hono 内置中间件

Hono 提供了 bearerAuth 中间件:

import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
 
const app = new Hono()
 
// 简单的 Bearer Token 验证
const token = 'my-secret-token'
 
app.use('/api/*', bearerAuth({ token }))
 
app.get('/api/profile', (c) => {
  return c.json({ message: 'Authenticated' })
})

这个中间件适合简单的 Token 验证(如 API Key),不适合 JWT。

2.3 结合 JWT

import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
 
const app = new Hono()
 
// JWT 中间件自动解析 Bearer Token
app.use('/api/*', jwt({
  secret: process.env.JWT_SECRET!,
  alg: 'HS256',
}))
 
app.get('/api/profile', (c) => {
  const payload = c.get('jwtPayload')
  return c.json(payload)
})

3. Token 类型

3.1 JWT

JWT 是最常见的 Bearer Token:

import { sign, verify } from 'hono/jwt'
 
// 签发
const token = await sign(
  {
    userId: 'user-1',
    email: '[email protected]',
    role: 'admin',
    exp: Math.floor(Date.now() / 1000) + 60 * 60,  // 1 小时
  },
  process.env.JWT_SECRET!
)
 
// 验证
const payload = await verify(token, process.env.JWT_SECRET!)

特点:

  • 无状态,服务端不需要存储
  • 自包含,Token 里有用户信息
  • 过期后需要刷新

3.2 OAuth Access Token

OAuth 流程返回的 Access Token:

// OAuth 回调
app.get('/auth/callback', async (c) => {
  const code = c.req.query('code')
 
  // 换取 Access Token
  const response = await fetch('https://oauth.provider.com/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      client_id: process.env.CLIENT_ID!,
      client_secret: process.env.CLIENT_SECRET!,
      redirect_uri: 'https://api.example.com/auth/callback',
    }),
  })
 
  const { access_token, token_type, expires_in } = await response.json()
 
  // token_type 通常是 "Bearer"
  // 存储 access_token,后续请求使用
})

3.3 API Key

API Key 也可以用 Bearer 方案:

app.use('/api/*', async (c, next) => {
  const authHeader = c.req.header('Authorization')
 
  if (!authHeader?.startsWith('Bearer ')) {
    return c.json({ error: 'Missing API key' }, 401)
  }
 
  const apiKey = authHeader.substring(7)
 
  // 验证 API Key
  const key = await validateApiKey(apiKey)
 
  if (!key) {
    return c.json({ error: 'Invalid API key' }, 401)
  }
 
  c.set('apiKey', key)
 
  await next()
})

4. Token 刷新

4.1 Access Token + Refresh Token

短期 Access Token + 长期 Refresh Token:

// 登录
app.post('/auth/login', async (c) => {
  const { email, password } = await c.req.json()
 
  const user = await verifyUser(email, password)
  if (!user) {
    return c.json({ error: 'Invalid credentials' }, 401)
  }
 
  // Access Token(15 分钟)
  const accessToken = await sign(
    {
      userId: user.id,
      type: 'access',
      exp: Math.floor(Date.now() / 1000) + 15 * 60,
    },
    process.env.JWT_SECRET!
  )
 
  // Refresh Token(7 天)
  const refreshToken = await sign(
    {
      userId: user.id,
      type: 'refresh',
      exp: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
    },
    process.env.JWT_SECRET!
  )
 
  // 存储 Refresh Token
  await db.insert(refreshTokens).values({
    userId: user.id,
    token: refreshToken,
    expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
  })
 
  return c.json({
    accessToken,
    refreshToken,
    expiresIn: 15 * 60,
  })
})

4.2 刷新逻辑

app.post('/auth/refresh', async (c) => {
  const { refreshToken } = await c.req.json()
 
  // 验证 Refresh Token
  try {
    const payload = await verify(refreshToken, process.env.JWT_SECRET!)
 
    if (payload.type !== 'refresh') {
      return c.json({ error: 'Invalid token type' }, 401)
    }
 
    // 检查是否在数据库中
    const stored = await db.query.refreshTokens.findFirst({
      where: eq(refreshTokens.token, refreshToken),
    })
 
    if (!stored || stored.expiresAt < new Date()) {
      return c.json({ error: 'Invalid refresh token' }, 401)
    }
 
    // 签发新的 Access Token
    const newAccessToken = await sign(
      {
        userId: payload.userId,
        type: 'access',
        exp: Math.floor(Date.now() / 1000) + 15 * 60,
      },
      process.env.JWT_SECRET!
    )
 
    return c.json({
      accessToken: newAccessToken,
      expiresIn: 15 * 60,
    })
  } catch {
    return c.json({ error: 'Invalid refresh token' }, 401)
  }
})

4.3 前端自动刷新

前端在 Access Token 过期前自动刷新:

// 前端代码
let accessToken = localStorage.getItem('accessToken')
let refreshToken = localStorage.getItem('refreshToken')
 
async function fetchWithAuth(url: string) {
  // 检查 Access Token 是否快过期
  if (isTokenExpiringSoon(accessToken)) {
    await refreshAccessToken()
  }
 
  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  })
 
  if (response.status === 401) {
    // Token 过期,尝试刷新
    await refreshAccessToken()
 
    // 重试请求
    return fetch(url, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    })
  }
 
  return response
}
 
async function refreshAccessToken() {
  const response = await fetch('/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken }),
  })
 
  const data = await response.json()
  accessToken = data.accessToken
  localStorage.setItem('accessToken', accessToken)
}
 
function isTokenExpiringSoon(token: string): boolean {
  const payload = JSON.parse(atob(token.split('.')[1]))
  const expiresAt = payload.exp * 1000
  const now = Date.now()
 
  // 5 分钟内过期
  return expiresAt - now < 5 * 60 * 1000
}

5. Token 撤销

5.1 黑名单

把撤销的 Token 加入黑名单:

// 登出时撤销 Token
app.post('/auth/logout', async (c) => {
  const user = c.get('user')
  const token = c.req.header('Authorization')?.substring(7)
 
  if (token) {
    // 把 Token 加入黑名单
    const payload = await verify(token, process.env.JWT_SECRET!)
    const expiresAt = payload.exp
 
    await redis.setex(
      `blacklist:${token}`,
      expiresAt - Math.floor(Date.now() / 1000),
      '1'
    )
  }
 
  // 删除 Refresh Token
  await db
    .delete(refreshTokens)
    .where(eq(refreshTokens.userId, user.userId))
 
  return c.json({ message: 'Logged out' })
})
 
// 验证时检查黑名单
app.use('/api/*', async (c, next) => {
  const token = c.req.header('Authorization')?.substring(7)
 
  // 检查黑名单
  const isBlacklisted = await redis.get(`blacklist:${token}`)
 
  if (isBlacklisted) {
    return c.json({ error: 'Token has been revoked' }, 401)
  }
 
  // 继续验证...
})

5.2 Token 版本

使用版本号撤销所有 Token:

// 用户表添加 tokenVersion 字段
export const users = pgTable('users', {
  id: uuid('id').primaryKey(),
  tokenVersion: integer('token_version').default(0).notNull(),
})
 
// 签发 Token 时包含版本号
const token = await sign(
  {
    userId: user.id,
    tokenVersion: user.tokenVersion,
    exp: Math.floor(Date.now() / 1000) + 60 * 60,
  },
  process.env.JWT_SECRET!
)
 
// 验证时检查版本号
app.use('/api/*', async (c, next) => {
  const payload = c.get('jwtPayload')
 
  const user = await db.query.users.findFirst({
    where: eq(users.id, payload.userId),
  })
 
  if (payload.tokenVersion !== user?.tokenVersion) {
    return c.json({ error: 'Token has been revoked' }, 401)
  }
 
  await next()
})
 
// 撤销所有 Token
app.post('/auth/revoke-all', async (c) => {
  const user = c.get('user')
 
  await db
    .update(users)
    .set({ tokenVersion: sql`${users.tokenVersion} + 1` })
    .where(eq(users.id, user.userId))
 
  return c.json({ message: 'All tokens revoked' })
})

6. 安全考虑

6.1 HTTPS

Bearer Token 必须通过 HTTPS 传输:

❌ HTTP 传输,Token 容易被窃听
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
 
✅ HTTPS 传输,加密保护
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

6.2 Token 存储

前端存储 Token 的方式:

// ❌ localStorage,容易被 XSS 攻击读取
localStorage.setItem('token', token)
 
// ✅ httpOnly Cookie,JavaScript 无法访问
setCookie(c, 'token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'Strict',
})
 
// ⚠️ 内存存储,刷新后丢失
let token = token  // 变量

推荐:

  • Web 应用:httpOnly Cookie(需要 CSRF 保护)
  • 移动应用:安全存储(Keychain、Keystore)
  • SPA:内存 + 自动刷新

6.3 Token 泄露

Token 泄露后的应对:

  1. 短过期时间:减少泄露窗口
  2. 刷新机制:定期更换 Token
  3. 撤销机制:立即撤销泄露的 Token
  4. 监控:检测异常使用

7. AI 项目的实践

7.1 OpenAI 风格

// 请求
POST /v1/chat/completions
Authorization: Bearer sk-proj-abc123...
 
// 验证
app.use('/v1/*', async (c, next) => {
  const authHeader = c.req.header('Authorization')
 
  if (!authHeader?.startsWith('Bearer ')) {
    return c.json({
      error: {
        message: 'Missing API key',
        type: 'invalid_request_error',
      },
    }, 401)
  }
 
  const apiKey = authHeader.substring(7)
 
  // 验证 API Key
  const key = await validateApiKey(apiKey)
 
  if (!key) {
    return c.json({
      error: {
        message: 'Invalid API key',
        type: 'authentication_error',
      },
    }, 401)
  }
 
  c.set('apiKey', key)
 
  await next()
})

7.2 响应格式

// 成功
{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "model": "gpt-4",
  "choices": [...]
}
 
// 错误
{
  "error": {
    "message": "Invalid API key",
    "type": "authentication_error",
    "code": "invalid_api_key"
  }
}

总结

Bearer Token 是最通用的 HTTP 认证方案。JWT、OAuth Token、API Key 都可以用 Bearer 方案传递。

这一节涉及到的几个实践:

  1. Bearer 原理Authorization: Bearer &lt;token&gt;
  2. Token 类型:JWT、OAuth Access Token、API Key
  3. Token 刷新:Access Token + Refresh Token
  4. Token 撤销:黑名单、版本号
  5. 安全考虑:HTTPS、存储方式、泄露应对
  6. AI 项目:OpenAI 风格的 API Key

Bearer Token 是 API 认证的标准方案。配合短过期时间、刷新机制和撤销机制,可以构建安全的认证系统。

下一篇看 RBAC 权限模型——角色定义、权限分配、角色继承。