主题
认证、授权与安全
以 Better Auth、Arcjet 和基于角色的授权为例
边界划分
- 认证:验证请求者身份,建立可信的会话上下文。
- 授权:依据角色、资源归属或业务策略,决定该身份能否执行操作。
- 运行时安全:在请求进入业务逻辑前处理限流、机器人流量和常见攻击。
Controller 只声明端点和输入;认证、授权与安全策略不应散落在每个 Controller 方法中。
Better Auth
Better Auth 是 TypeScript 认证与授权框架,可管理用户、账号、会话和验证记录。它可以通过 Prisma 或 Drizzle adapter 使用现有 PostgreSQL 数据库。
最少配置包括:高熵的 BETTER_AUTH_SECRET、明确的 BETTER_AUTH_URL、数据库 adapter 和所需登录方式。认证服务拥有其表结构和迁移;业务数据中的用户 ID 只作为关联键,不复制密码、会话或第三方账号凭据。
bash
pnpm dlx @better-auth/cli@latest secret将命令生成的值写入 .env,不要自己编造短密钥:
md
BETTER_AUTH_SECRET=替换为命令生成的高熵密钥
BETTER_AUTH_URL=http://localhost:3000ts
import { betterAuth } from 'better-auth'
import { prismaAdapter } from 'better-auth/adapters/prisma'
import { prisma } from './prisma'
export const auth = betterAuth({
baseURL: process.env.BETTER_AUTH_URL,
database: prismaAdapter(prisma, {
provider: 'postgresql',
}),
emailAndPassword: {
enabled: true,
},
})NestJS 需要将认证端点挂载到应用,并把经过验证的会话转换为请求上下文。使用 Better Auth 的 NestJS 集成时,应按集成文档关闭默认 body parser,再由 Auth Module 接管对应认证路由;否则请求体可能被重复消费。
ts
const app = await NestFactory.create(AppModule, {
bodyParser: false,
})官方 NestJS 集成使用全局 AuthGuard,再通过 @AllowAnonymous() 显式标记公开端点;也可以使用自定义 Guard,但必须统一处理会话解析、失败响应和当前用户注入。
RBAC
角色信息是授权输入,不是认证结果。将角色定义集中在领域模型中,并通过元数据声明端点所需角色:
ts
import { SetMetadata } from '@nestjs/common'
export const ROLES_KEY = 'roles'
export const Roles = (...roles: Array<'admin' | 'participant'>) => SetMetadata(ROLES_KEY, roles)ts
@Post()
@Roles('admin')
create(@Body() input: CreateHackathonDto) {
return this.hackathonsService.create(input)
}RolesGuard 读取元数据和当前用户角色后做出决定。仅检查角色不足以覆盖所有场景:更新或删除资源时,还应在 Service 中验证资源归属、状态和业务约束。
课程的角色模型只允许 participant 与 admin,注册时固定为 participant,并且不能由注册请求传入。管理员需由受控的管理流程或数据库操作提升,不能暴露“修改自己角色”的公开接口。
Arcjet
Arcjet 可作为请求进入应用前的运行时安全层,提供限流、机器人检测和 Shield WAF 等规则。NestJS 集成可注册全局模块或 Guard;安全决策应在 Controller 和数据库访问之前执行。
配置原则:
- 以
ARCJET_KEY等环境变量提供密钥,绝不写入源码。 - 限流维度按端点和身份选择:匿名端点通常按 IP,已登录端点优先按用户 ID。
- 首次上线使用
DRY_RUN观察误拦截、允许规则和流量阈值,再切换为阻断模式。 - 命中限流返回
429,命中机器人或 WAF 规则通常返回403;记录决策原因,不记录敏感请求内容。
课程从全局 Shield 与固定窗口限流开始。ARCJET_MODE=dry-run 先只记录决策,切换为 live 后才执行阻断;示例阈值为每个 IP 60 秒 10 次请求。不同 Arcjet 规则的拒绝状态码可能不同,应以实际规则结果和 SDK 文档为准。
Arcjet 是纵深防御的一层,不能替代输入校验、认证、授权或数据库约束。
公开端点与最小权限
登录、注册、健康检查和公开读取端点必须显式列出;其余端点默认需要认证。每个写操作都应检查:请求者身份、角色、资源归属和资源当前状态。
