安全插件(sentinel)
`sentinel()` 插件为你的身份验证系统提供全面的安全和滥用防护。它能够检测并防止多种攻击手段,包括凭证填充、不可能的旅行、免费试用滥用等
安装
import { betterAuth } from "better-auth";
import { sentinel } from "@better-auth/infra";
export const auth = betterAuth({
plugins: [
sentinel({
security: {
// 在此配置安全功能
},
}),
],
});配置选项
SentinelOptions
| 选项 | 类型 | 描述 |
|---|---|---|
apiUrl | string | Better Auth Infrastructure API URL。默认值:https://dash.better-auth.com |
kvUrl | string | Better Auth 身份识别基础设施 URL。默认值:https://kv.better-auth.com |
apiKey | string | 用于身份验证的 API 密钥。未设置时回退到环境变量 BETTER_AUTH_API_KEY。 |
apiOptions | object | Dash API HTTP 选项。接受 timeout?: number(毫秒,默认 3000)和 headers?: Record<string, string>。 |
kvOptions | object | KV HTTP 选项。接受 timeout?: number(毫秒,默认 1000)、retry?: { attempts?: number; baseDelay?: number; maxDelay?: number } 和 headers?: Record<string, string>。 |
apiTimeout | number | apiOptions.timeout 的弃用别名。 |
kvTimeout | number | kvOptions.timeout 的弃用别名。 |
security | SecurityOptions | 安全功能配置。 |
SecurityOptions
interface SecurityOptions {
unknownDeviceNotification?: boolean;
credentialStuffing?: CredentialStuffingConfig;
impossibleTravel?: ImpossibleTravelConfig;
geoBlocking?: GeoBlockingConfig;
botBlocking?: boolean | { action: SecurityAction };
suspiciousIpBlocking?: boolean | { action: SecurityAction };
velocity?: VelocityConfig;
freeTrialAbuse?: FreeTrialAbuseConfig;
compromisedPassword?: CompromisedPasswordConfig;
emailValidation?: EmailValidationConfig;
emailNormalization?: { enabled?: boolean };
staleUsers?: StaleUsersConfig;
challengeDifficulty?: number;
}
type SecurityAction = "log" | "challenge" | "block";安全功能
凭证填充防护
通过跟踪每个访客的登录失败次数,检测并阻止凭证填充攻击。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
credentialStuffing: {
enabled: true,
action: "challenge", // optional default action; thresholds still drive challenge vs block
thresholds: {
challenge: 3, // 失败 3 次后发起 PoW 挑战
block: 5, // 失败 5 次后封禁
},
windowSeconds: 3600, // 1 小时时间窗口
cooldownSeconds: 900, // 封禁后 15 分钟冷却期
},
},
}),工作原理:
- 跟踪每个访客 ID 的登录失败尝试次数
- 达到挑战阈值后,发出工作量证明(PoW)挑战
- 达到封禁阈值后,完全封禁访客
- 登录成功后自动清除失败尝试记录
不可能的旅行检测
检测用户在极短时间内从地理上相距遥远的地方登录。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
impossibleTravel: {
enabled: true,
maxSpeedKmh: 1000, // 最大合理旅行速度
action: "challenge", // 可选:"log"、"challenge"或"block"
},
},
}),示例: 如果用户先后在纽约和 30 分钟后在东京登录,这被视为不可能的旅行(即需要超过 1000 公里/小时的速度)。
免费试用滥用防护
通过设备指纹防止用户创建多个账户以滥用免费试用。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
freeTrialAbuse: {
enabled: true,
thresholds: {
challenge: 2,
block: 3,
},
maxAccountsPerVisitor: 3,
retentionSeconds: 90 * 24 * 60 * 60, // optional; default 90 days
action: "block",
},
},
}),工作原理:
- 跟踪每个访客指纹的账户创建次数
- 当超过阈值时,阻止新账户创建
- 用于防止免费层滥用
retentionSeconds 控制每位访客的注册记录键保留时长(滑动窗口)。默认值为 90 天;平台会将值限制在 1 天到 365 天之间。
已泄露密码检测
通过查询 HaveIBeenPwned 数据库检查密码,检测泄露凭证。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
compromisedPassword: {
enabled: true,
action: "block", // 可选:"log"、"challenge"或"block"
minBreachCount: 1, // 触发的最小泄露次数
},
},
}),隐私说明: 采用 k-匿名技术 — 仅将密码哈希的前 5 个字符发送到 API,完整密码绝不发送。
过期账户监控
检测沉睡账户突然活跃,可能表示账户被接管。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
staleUsers: {
enabled: true,
staleDays: 90, // 账户超过 90 天视为沉睡
action: "log", // 可选:"log"、"challenge"或"block"
notifyUser: true, // 向用户发送邮件通知
notifyAdmin: true, // 向管理员发送邮件通知
adminEmail: "[email protected]",
},
},
}),通知包括:
- 登录时间和位置
- 距离上次活跃的天数
- 设备信息
地理封禁
封禁或挑战特定国家的用户。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
geoBlocking: {
allowList: ["US", "CA", "GB"], // 仅允许这些国家
// 或者
denyList: ["XX", "YY"], // 封禁这些国家
action: "block", // 可选:"challenge"或"block"
},
},
}),使用 ISO 3166-1 alpha-2 国家代码。
机器人封禁
检测并阻止自动化机器流量。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
botBlocking: true,
// 或使用自定义操作
botBlocking: {
action: "challenge", // 可选:"log"、"challenge"或"block"
},
},
}),可疑 IP 检测
封禁来自已知恶意 IP 地址的请求。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
suspiciousIpBlocking: true,
// 或使用自定义操作
suspiciousIpBlocking: {
action: "block",
},
},
}),访问速率限制 / 频率控制
限制各种操作的请求速率。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
velocity: {
enabled: true,
thresholds: {
challenge: 10,
block: 20,
},
maxSignupsPerVisitor: 5,
maxPasswordResetsPerIp: 10,
maxSignInsPerIp: 50,
windowSeconds: 3600,
action: "challenge",
},
},
}),邮箱验证
封禁一次性邮箱地址并验证邮箱域名。
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
emailValidation: {
enabled: true,
strictness: "medium", // 可选:"low"、"medium"、"high"
action: "block",
domainAllowlist: ["company.com", "partner.org"], // always allow these domains
},
},
}),严格级别:
low- 仅阻止已知一次性域名medium- 还检查有效的 MX 记录high- 额外的启发式检查
domainAllowlist 会跳过所列域名的一次性邮箱和启发式检查(必须精确匹配域名)。
邮箱规范化
Sentinel 会在注册和登录之前规范化邮箱地址,以防止别名和提供商的怪癖导致重复账户或登录不匹配。规范化包括将邮箱转为小写、在常见提供商上移除 plus-address 标签(例如 [email protected] → [email protected])、移除 Gmail 风格地址中的点,以及将 googlemail.com 映射到 gmail.com。
当你希望将此功能与一次性域名验证(emailValidation)分开控制时,请使用 security.emailNormalization:
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
emailValidation: {
enabled: false, // 跳过一次性 / MX 检查
},
emailNormalization: {
enabled: true, // 仍进行规范化以去重和保持一致的登录
},
},
}),工作量证明挑战
当安全检查导致“challenge”动作时,Sentinel 会发起工作量证明(PoW)挑战,客户端必须完成挑战。
PoW 工作原理
- 服务器发出加密挑战
- 客户端必须找到满足难度要求的解答
- 解答计算开销大,但验证快速
- 有效阻止自动化攻击,允许合法用户通过
挑战难度
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
challengeDifficulty: 18, // 默认难度等级
},
}),难度越高 = 计算量越大 = 攻击者越慢。
客户端集成
sentinelClient()
客户端插件负责设备指纹识别及自动完成 PoW 挑战。
import { createAuthClient } from "better-auth/client";
import { sentinelClient } from "@better-auth/infra/client";
export const authClient = createAuthClient({
plugins: [
sentinelClient({
autoSolveChallenge: true,
}),
],
});配置项
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
identifyUrl | string | BETTER_AUTH_KV_URL 或 https://kv.better-auth.com | identify 端点的基础 URL。 |
identifyOptions | object | { timeout: 1000, retry: { attempts: 2, baseDelay: 400, maxDelay: 600 } } | Identify HTTP 超时与重试策略。 |
autoSolveChallenge | boolean | true | 自动解决 PoW 挑战。 |
kvTimeout | number | 1000 | identifyOptions.timeout 的弃用别名。 |
onChallengeReceived | (reason: string) => void | — | 收到 PoW 挑战时调用。 |
onChallengeSolved | (solveTimeMs: number) => void | — | 成功解决挑战后调用。 |
onChallengeFailed | (error: Error) => void | — | 解决挑战失败时调用。 |
浏览器指纹
客户端自动在请求中通过 X-Visitor-Id 头包含访客 ID。此指纹用于:
- 凭证填充检测
- 免费试用滥用防护
- 设备跟踪
PoW 解答请求头
启用自动解决后,解答通过 X-PoW-Solution 头发送。
Expo 和 React Native
对于 Expo 和 React Native 应用,使用 @better-auth/infra/native 中的 sentinelNativeClient,而不是使用 @better-auth/infra/client。
对于 React Native 应用,安装以下对等依赖:
npm install @react-native-async-storage/async-storage react-native-get-random-values@react-native-async-storage/async-storage 是可选的。如果未安装,客户端使用仅会话的内存访客 ID。对于生产环境,请安装它或传递自定义 storage(例如安全存储)。
示例
import { createAuthClient } from "better-auth/client";
import { dashClient, sentinelNativeClient } from "@better-auth/infra/native";
export const authClient = createAuthClient({
baseURL: "https://your-api.example.com",
plugins: [
dashClient(),
sentinelNativeClient({
autoSolveChallenge: true,
}),
],
});对于 Expo 应用,安装以下软件包以获得更丰富的原生身份信息。
Expo: 在添加基础设施插件之前,请遵循 Expo 集成 指南。
npm install expo-constants expo-device expo-crypto示例
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
import { dashClient, sentinelNativeClient } from "@better-auth/infra/native";
import * as SecureStore from "expo-secure-store";
export const authClient = createAuthClient({
baseURL: "https://your-api.example.com",
plugins: [
expoClient({
scheme: "myapp",
storagePrefix: "myapp",
storage: SecureStore,
}),
dashClient(),
sentinelNativeClient({
autoSolveChallenge: true,
}),
],
});sentinelNativeClient 选项
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
identifyUrl | string | BETTER_AUTH_KV_URL,然后是 https://kv.better-auth.com | Identify 端点基础 URL |
identifyOptions | object | { timeout: 1000, retry: { attempts: 2, baseDelay: 400, maxDelay: 600 } } | Identify HTTP 超时和重试策略 |
kvTimeout | number | 1000 | identifyOptions.timeout 的弃用别名 |
autoSolveChallenge | boolean | true | 遇到带有 X-PoW-Challenge 的 423 时,解决挑战并使用 X-PoW-Solution 重试一次 |
onChallengeReceived | (reason: string) => void | — | 收到 PoW 挑战时调用 |
onChallengeSolved | (solveTimeMs: number) => void | — | 成功解决挑战后调用 |
onChallengeFailed | (error: Error) => void | — | 解决失败时调用 |
storage | { getItem, setItem } | 安装后使用 Async Storage | 用于生成稳定的每次安装一个访客 ID 的持久异步存储 |
安全事件
Sentinel 跟踪以下安全事件类型:
| 事件类型 | 描述 |
|---|---|
security_blocked | 请求被阻止 |
security_allowed | 挑战后请求被允许 |
security_credential_stuffing | 检测到凭证填充 |
security_impossible_travel | 检测到不可能的旅行 |
security_geo_blocked | 触发地理封禁 |
security_bot_blocked | 检测并阻止机器人 |
security_suspicious_ip | 检测到可疑 IP |
security_velocity_exceeded | 超过速率限制 |
security_free_trial_abuse | 检测到免费试用滥用 |
security_compromised_password | 检测到泄露密码 |
security_stale_account | 沉睡账户重新激活 |
这些事件可在安全控制台查看,并包含在审计日志中。
完整示例
import { betterAuth } from "better-auth";
import { sentinel } from "@better-auth/infra";
export const auth = betterAuth({
plugins: [
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
// 核心防护
credentialStuffing: {
enabled: true,
thresholds: { challenge: 3, block: 5 },
},
compromisedPassword: {
enabled: true,
action: "block",
},
emailValidation: {
enabled: true,
strictness: "medium",
action: "block",
},
// 基于位置的策略
impossibleTravel: {
enabled: true,
action: "challenge",
},
geoBlocking: {
denyList: ["XX"],
action: "block",
},
// 滥用防护
freeTrialAbuse: {
enabled: true,
maxAccountsPerVisitor: 3,
action: "block",
},
velocity: {
enabled: true,
maxSignupsPerVisitor: 5,
action: "challenge",
},
// 机器人防护
botBlocking: { action: "challenge" },
suspiciousIpBlocking: { action: "block" },
// 账户监控
staleUsers: {
enabled: true,
staleDays: 90,
notifyUser: true,
notifyAdmin: true,
adminEmail: "[email protected]",
},
},
}),
],
});最佳实践
-
先从日志开始 - 初期将动作设置为“log”,以了解流量模式,再决定是否封禁。
-
调整阈值 - 每个应用都不同,监控误报率并相应调整阈值。
-
先使用挑战,后使用封禁 - 挑战能阻挡自动化攻击,同时允许合法用户访问。
-
启用客户端自动解决 - 在浏览器中使用
sentinelClient({ autoSolveChallenge: true }),或在 Expo / React Native 中使用sentinelNativeClient({ autoSolveChallenge: true })。 -
监控安全事件 - 定期查看安全控制台,识别攻击趋势。
-
设置管理员通知 - 对关键事件如沉睡账户激活启用
notifyAdmin。
疑难解答
缺少 API 密钥警告
[Sentinel] 缺少 BETTER_AUTH_API_KEY。安全检查可能回退到允许模式。请确保正确设置了 API 密钥环境变量。
挑战无法执行
如果 PoW 挑战无法解决:
- 验证正确的客户端插件:Web 使用
sentinelClient()(来自@better-auth/infra/client),Expo / React Native 使用sentinelNativeClient()(来自@better-auth/infra/native) - 检查
autoSolveChallenge是否为true - 确保客户端可以访问服务器
误报率过高
如果合法用户被阻止:
- 增加阈值
- 将操作从“block”改为“challenge”
- 查看安全事件以识别模式