开发者指南
API Key开发指南
架构概述
API Key 系统由以下关键组件组成:
- API Key 管理:创建、验证和撤销 API Key
- 身份验证中间件:验证传入请求
- 权限控制:基于角色的访问控制
- 速率限制:防止滥用
- 审计日志:跟踪 API 使用情况
数据库架构
API Key 系统使用以下数据库表:
API Key 表
CREATE TABLE api_keys (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES user(id),
name TEXT NOT NULL,
key_value TEXT NOT NULL UNIQUE,
permissions JSONB DEFAULT '[]',
last_used_at TIMESTAMP,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_api_keys_key_value ON api_keys(key_value);
CREATE INDEX idx_api_keys_user_id ON api_keys(user_id);
API 使用日志表
CREATE TABLE api_usage_logs (
id TEXT PRIMARY KEY,
api_key_id TEXT NOT NULL REFERENCES api_keys(id),
endpoint TEXT NOT NULL,
method TEXT NOT NULL,
status_code INTEGER NOT NULL,
response_time_ms INTEGER,
ip_address TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_api_usage_logs_api_key_id ON api_usage_logs(api_key_id);
CREATE INDEX idx_api_usage_logs_created_at ON api_usage_logs(created_at);
核心服务
API Key 服务
主要的 API Key 服务处理所有 API Key 操作:
// src/lib/api-keys/api-key-service.ts
import { db } from '@/server/db'
import { apiKeys, apiUsageLogs } from '@/server/db/schema'
import { eq, and } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
import crypto from 'crypto'
export class ApiKeyService {
static generateApiKey(): string {
const prefix = 'bs_'
const randomBytes = crypto.randomBytes(32).toString('hex')
return `${prefix}${randomBytes}`
}
static async createApiKey(
userId: string,
name: string,
permissions: string[] = [],
expiresAt?: Date
) {
const keyValue = this.generateApiKey()
const id = uuidv4()
const [apiKey] = await db
.insert(apiKeys)
.values({
id,
userId,
name,
keyValue,
permissions,
expiresAt
})
.returning()
return { ...apiKey, keyValue }
}
static async validateApiKey(keyValue: string) {
const [apiKey] = await db
.select()
.from(apiKeys)
.where(
and(
eq(apiKeys.keyValue, keyValue),
// 检查是否过期
apiKeys.expiresAt ? sql`expires_at > NOW()` : undefined
)
)
.limit(1)
if (!apiKey) {
return null
}
// 更新最后使用时间
await db
.update(apiKeys)
.set({ lastUsedAt: new Date() })
.where(eq(apiKeys.id, apiKey.id))
return apiKey
}
static async getUserApiKeys(userId: string) {
return await db
.select({
id: apiKeys.id,
name: apiKeys.name,
permissions: apiKeys.permissions,
lastUsedAt: apiKeys.lastUsedAt,
expiresAt: apiKeys.expiresAt,
createdAt: apiKeys.createdAt
})
.from(apiKeys)
.where(eq(apiKeys.userId, userId))
.orderBy(desc(apiKeys.createdAt))
}
static async deleteApiKey(userId: string, keyId: string) {
await db
.delete(apiKeys)
.where(
and(
eq(apiKeys.id, keyId),
eq(apiKeys.userId, userId)
)
)
}
static async logApiUsage(
apiKeyId: string,
endpoint: string,
method: string,
statusCode: number,
responseTimeMs: number,
ipAddress?: string,
userAgent?: string
) {
await db.insert(apiUsageLogs).values({
id: uuidv4(),
apiKeyId,
endpoint,
method,
statusCode,
responseTimeMs,
ipAddress,
userAgent
})
}
}
身份验证中间件
用于验证 API 请求的中间件:
// src/middleware/api-auth.ts
import { NextRequest, NextResponse } from 'next/server'
import { ApiKeyService } from '@/lib/api-keys/api-key-service'
export async function apiAuthMiddleware(
request: NextRequest,
requiredPermissions: string[] = []
) {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ error: '缺少或无效的授权头' },
{ status: 401 }
)
}
const apiKey = authHeader.substring(7) // 移除 'Bearer ' 前缀
const validatedKey = await ApiKeyService.validateApiKey(apiKey)
if (!validatedKey) {
return NextResponse.json(
{ error: '无效或过期的 API Key' },
{ status: 401 }
)
}
// 检查权限
if (requiredPermissions.length > 0) {
const hasPermission = requiredPermissions.every(permission =>
validatedKey.permissions.includes(permission)
)
if (!hasPermission) {
return NextResponse.json(
{ error: '权限不足' },
{ status: 403 }
)
}
}
// 将 API Key 信息添加到请求中
request.apiKey = validatedKey
return null // 继续处理请求
}
API 端点
创建 API Key
// src/app/api/api-keys/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/server/auth'
import { ApiKeyService } from '@/lib/api-keys/api-key-service'
export async function POST(request: NextRequest) {
const session = await auth.api.getSession({ headers: request.headers })
if (!session?.user) {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
const { name, permissions, expiresAt } = await request.json()
if (!name || typeof name !== 'string') {
return NextResponse.json(
{ error: '名称是必需的' },
{ status: 400 }
)
}
try {
const apiKey = await ApiKeyService.createApiKey(
session.user.id,
name,
permissions,
expiresAt ? new Date(expiresAt) : undefined
)
return NextResponse.json({
id: apiKey.id,
name: apiKey.name,
keyValue: apiKey.keyValue, // 只在创建时返回
permissions: apiKey.permissions,
expiresAt: apiKey.expiresAt,
createdAt: apiKey.createdAt
})
} catch (error) {
console.error('创建 API Key 失败:', error)
return NextResponse.json(
{ error: '创建 API Key 失败' },
{ status: 500 }
)
}
}
export async function GET(request: NextRequest) {
const session = await auth.api.getSession({ headers: request.headers })
if (!session?.user) {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
try {
const apiKeys = await ApiKeyService.getUserApiKeys(session.user.id)
return NextResponse.json({ apiKeys })
} catch (error) {
console.error('获取 API Key 失败:', error)
return NextResponse.json(
{ error: '获取 API Key 失败' },
{ status: 500 }
)
}
}
删除 API Key
// src/app/api/api-keys/[keyId]/route.ts
export async function DELETE(
request: NextRequest,
{ params }: { params: { keyId: string } }
) {
const session = await auth.api.getSession({ headers: request.headers })
if (!session?.user) {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
try {
await ApiKeyService.deleteApiKey(session.user.id, params.keyId)
return NextResponse.json({ success: true })
} catch (error) {
console.error('删除 API Key 失败:', error)
return NextResponse.json(
{ error: '删除 API Key 失败' },
{ status: 500 }
)
}
}
外部 API 端点示例
// src/app/api/external/chat/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { apiAuthMiddleware } from '@/middleware/api-auth'
import { QuotaService } from '@/lib/quota/quota-service'
export async function POST(request: NextRequest) {
// 验证 API Key
const authError = await apiAuthMiddleware(request, ['chat:create'])
if (authError) return authError
const { message } = await request.json()
if (!message) {
return NextResponse.json(
{ error: '消息是必需的' },
{ status: 400 }
)
}
try {
// 检查配额
const quota = await QuotaService.checkQuota(
request.apiKey.userId,
'ai_chat_message'
)
if (!quota.allowed) {
return NextResponse.json(
{ error: '配额不足', required: quota.cost },
{ status: 402 }
)
}
// 处理聊天请求
const response = await processChatMessage(message)
// 消耗配额
await QuotaService.consumeQuota(
request.apiKey.userId,
'ai_chat_message'
)
// 记录 API 使用情况
await ApiKeyService.logApiUsage(
request.apiKey.id,
'/api/external/chat',
'POST',
200,
Date.now() - startTime,
request.ip,
request.headers.get('user-agent')
)
return NextResponse.json({ response })
} catch (error) {
console.error('聊天 API 错误:', error)
return NextResponse.json(
{ error: '内部服务器错误' },
{ status: 500 }
)
}
}
React 组件
API Key 管理组件
// src/components/api-keys/api-key-manager.tsx
'use client'
import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
interface ApiKey {
id: string
name: string
permissions: string[]
lastUsedAt: string | null
expiresAt: string | null
createdAt: string
}
export function ApiKeyManager() {
const [apiKeys, setApiKeys] = useState<ApiKey[]>([])
const [newKeyName, setNewKeyName] = useState('')
const [isCreating, setIsCreating] = useState(false)
const [newApiKey, setNewApiKey] = useState<string | null>(null)
useEffect(() => {
fetchApiKeys()
}, [])
const fetchApiKeys = async () => {
try {
const response = await fetch('/api/api-keys')
const data = await response.json()
setApiKeys(data.apiKeys)
} catch (error) {
console.error('获取 API Key 失败:', error)
}
}
const createApiKey = async () => {
if (!newKeyName.trim()) return
setIsCreating(true)
try {
const response = await fetch('/api/api-keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: newKeyName,
permissions: ['chat:create', 'files:upload']
})
})
const data = await response.json()
if (response.ok) {
setNewApiKey(data.keyValue)
setNewKeyName('')
fetchApiKeys()
} else {
console.error('创建失败:', data.error)
}
} catch (error) {
console.error('创建 API Key 失败:', error)
} finally {
setIsCreating(false)
}
}
const deleteApiKey = async (keyId: string) => {
try {
const response = await fetch(`/api/api-keys/${keyId}`, {
method: 'DELETE'
})
if (response.ok) {
fetchApiKeys()
}
} catch (error) {
console.error('删除 API Key 失败:', error)
}
}
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>创建新的 API Key</CardTitle>
</CardHeader>
<CardContent>
<div className="flex gap-2">
<Input
placeholder="API Key 名称"
value={newKeyName}
onChange={(e) => setNewKeyName(e.target.value)}
/>
<Button
onClick={createApiKey}
disabled={isCreating || !newKeyName.trim()}
>
{isCreating ? '创建中...' : '创建'}
</Button>
</div>
{newApiKey && (
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded">
<p className="text-sm font-medium text-yellow-800 mb-2">
您的新 API Key(请安全保存,不会再次显示):
</p>
<code className="block p-2 bg-white border rounded text-sm break-all">
{newApiKey}
</code>
</div>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>现有 API Keys</CardTitle>
</CardHeader>
<CardContent>
{apiKeys.length === 0 ? (
<p className="text-gray-500">还没有 API Key</p>
) : (
<div className="space-y-4">
{apiKeys.map((key) => (
<div
key={key.id}
className="flex items-center justify-between p-4 border rounded"
>
<div>
<h3 className="font-medium">{key.name}</h3>
<p className="text-sm text-gray-500">
创建于: {new Date(key.createdAt).toLocaleDateString()}
</p>
{key.lastUsedAt && (
<p className="text-sm text-gray-500">
最后使用: {new Date(key.lastUsedAt).toLocaleDateString()}
</p>
)}
</div>
<Button
variant="destructive"
size="sm"
onClick={() => deleteApiKey(key.id)}
>
删除
</Button>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
)
}
安全最佳实践
- API Key 格式:使用可识别的前缀(如
bs_
) - 加密存储:在数据库中哈希存储 API Key
- 权限控制:实现细粒度权限系统
- 速率限制:防止 API 滥用
- 审计日志:记录所有 API 使用情况
- 过期时间:为 API Key 设置合理的过期时间
- IP 白名单:可选的 IP 地址限制
使用示例
客户端使用
// 使用 API Key 调用外部 API
const response = await fetch('https://your-app.com/api/external/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer bs_your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'Hello, AI!'
})
})
const data = await response.json()
console.log(data.response)
cURL 示例
curl -X POST https://your-app.com/api/external/chat \
-H "Authorization: Bearer bs_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"message": "Hello, AI!"}'
测试
// tests/unit/api-key-service.test.ts
import { ApiKeyService } from '@/lib/api-keys/api-key-service'
describe('ApiKeyService', () => {
it('应该生成有效的 API Key', () => {
const apiKey = ApiKeyService.generateApiKey()
expect(apiKey).toMatch(/^bs_[a-f0-9]{64}$/)
})
it('应该创建和验证 API Key', async () => {
const userId = 'test-user'
const name = '测试 Key'
const createdKey = await ApiKeyService.createApiKey(userId, name)
expect(createdKey.name).toBe(name)
expect(createdKey.keyValue).toBeDefined()
const validatedKey = await ApiKeyService.validateApiKey(createdKey.keyValue)
expect(validatedKey).toBeTruthy()
expect(validatedKey.userId).toBe(userId)
})
it('应该拒绝无效的 API Key', async () => {
const invalidKey = await ApiKeyService.validateApiKey('invalid_key')
expect(invalidKey).toBeNull()
})
})
这个 API Key 系统为您的 SaaS 应用程序提供了安全、可扩展的程序化访问解决方案。