Documentation Index
Fetch the complete documentation index at: https://bun.zhcndoc.com/llms.txt
Use this file to discover all available pages before exploring further.
Bun 通过 Bun.CSRF 提供了一个内置 API,用于生成和验证 CSRF(跨站请求伪造)令牌。令牌使用 HMAC 签名,并包含过期时间戳以限制令牌的有效期窗口。
csrf.ts// 生成一个与请求者会话绑定的令牌
const token = Bun.CSRF.generate("my-secret", { sessionId: "user-session-id" });
// 验证它
const isValid = Bun.CSRF.verify(token, { secret: "my-secret", sessionId: "user-session-id" });
console.log(isValid); // true
始终同时将 sessionId(请求者的会话标识符或用户 ID)传递给 generate() 和 verify()。如果不传递,
令牌就只与密钥绑定——服务器曾签发过的任何令牌都对每个用户有效,因此攻击者可以在自己的会话中获取一个令牌,
并在受害者浏览器发起的伪造跨站请求中重放它。
Bun.CSRF.generate()
生成 CSRF 令牌。令牌包含加密随机数(nonce)、时间戳和 HMAC 签名,编码为字符串。
generate.tsconst token = Bun.CSRF.generate("my-secret-key");
参数:
secret(string,可选)— 用于签署令牌的密钥。如果未提供,Bun 会生成一个随机的内存中默认密钥(每个线程唯一)。
options(object,可选):
| Option | Type | Default | Description |
|---|
expiresIn | number | 86400000 | 令牌过期前的毫秒数。默认是 24 小时。 |
encoding | string | "base64url" | 令牌编码格式:"base64"、"base64url" 或 "hex"。 |
algorithm | string | "sha256" | HMAC 算法:"sha256"、"sha384"、"sha512"、"sha512-256"、"blake2b256" 或 "blake2b512"。 |
sessionId | string | (none) | 将令牌绑定到请求主体(会话 ID、用户 ID 或等效标识)。只有在 verify() 中传入相同的 sessionId 时,令牌才会验证通过。 |
返回: string — 编码后的令牌。
generate-options.ts// 绑定到请求者会话的令牌,1 小时后过期,编码为 hex
const token = Bun.CSRF.generate("my-secret", {
sessionId: "user-session-id",
expiresIn: 60 * 60 * 1000,
encoding: "hex",
});
// 使用不同的算法
const token2 = Bun.CSRF.generate("my-secret", {
sessionId: "user-session-id",
algorithm: "sha512",
});
Bun.CSRF.verify()
验证 CSRF 令牌。如果令牌有效且未过期,返回 true,否则返回 false。
verify.tsconst isValid = Bun.CSRF.verify(token, { secret: "my-secret-key" });
参数:
token(string,必需)— 要验证的令牌。
options(object,可选):
| Option | Type | Default | Description |
|---|
secret | string | (auto) | 用于签署令牌的密钥。如果未提供,则使用与 generate() 相同的内存默认密钥。 |
maxAge | number | 86400000 | 令牌的最大年龄(毫秒),独立于令牌自身的 expiresIn。 |
encoding | string | "base64url" | 必须与 generate() 中使用的编码一致。 |
algorithm | string | "sha256" | 必须与 generate() 中使用的算法一致。 |
sessionId | string | (none) | 必须与 generate() 中使用的 sessionId 一致。绑定到某一主体的令牌会对任何其他主体验证失败;而在生成时未提供 sessionId 的令牌,如果验证时提供了 sessionId,也会验证失败。 |
返回: boolean
verify-options.ts// 验证一个绑定到请求者会话的令牌
const isValid = Bun.CSRF.verify(token, {
secret: "my-secret",
sessionId: "user-session-id",
});
// 强制执行比生成令牌时更短的最大存活时间
const isValid2 = Bun.CSRF.verify(token, {
secret: "my-secret",
sessionId: "user-session-id",
maxAge: 60 * 1000, // 拒绝超过 1 分钟的令牌
});
在 Bun.serve() 中使用
一个典型模式是在渲染表单时生成令牌,将其嵌入隐藏字段,并在表单提交时验证它。将请求者的会话标识符作为 sessionId 传递给这两次调用,这样令牌只对签发给它的用户有效。
server.tsconst SECRET = process.env.CSRF_SECRET || "my-secret";
// 从会话 cookie 中解析请求者的会话标识符。
// 当访问者还没有会话时返回 null——切勿退回到共享的占位符,
// 否则所有没有会话的访问者都会共享同一个令牌绑定。
function getSessionId(req: Request): string | null {
return req.headers.get("cookie")?.match(/(?:^|;\s*)session=([^;]+)/)?.[1] ?? null;
}
const server = Bun.serve({
routes: {
"/form": req => {
// 在签发表单前为每个访问者创建一个会话,
// 这样令牌就会绑定到这个访问者,而不是其他任何人。
let sessionId = getSessionId(req);
const headers = new Headers({ "Content-Type": "text/html" });
if (!sessionId) {
sessionId = crypto.randomUUID();
headers.append("Set-Cookie", `session=${sessionId}; HttpOnly; SameSite=Lax; Path=/`);
}
const token = Bun.CSRF.generate(SECRET, { sessionId });
return new Response(
`<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="${token}" />
<input type="text" name="message" />
<button type="submit">发送</button>
</form>`,
{ headers },
);
},
"/submit": {
POST: async req => {
const sessionId = getSessionId(req);
const formData = await req.formData();
const csrfToken = formData.get("_csrf");
if (!sessionId || typeof csrfToken !== "string" || !Bun.CSRF.verify(csrfToken, { secret: SECRET, sessionId })) {
return new Response("无效的 CSRF 令牌", { status: 403 });
}
return new Response("成功");
},
},
},
});
console.log(`监听于 ${server.url}`);
默认密钥
如果在 generate() 和 verify() 中都省略 secret 参数,Bun 会使用每个线程生成一次的随机密钥。这对于单线程应用很方便,但在多台服务器、多个 Worker 之间或重启后无法工作。
default-secret.ts// 这两个调用在此运行时上下文中使用相同的每线程默认密钥。
const token = Bun.CSRF.generate();
const isValid = Bun.CSRF.verify(token); // true
对于生产环境,请始终提供跨基础设施共享的显式密钥。
TypeScript
types.tstype CSRFAlgorithm = "blake2b256" | "blake2b512" | "sha256" | "sha384" | "sha512" | "sha512-256";
interface CSRFGenerateOptions {
expiresIn?: number;
encoding?: "base64" | "base64url" | "hex";
algorithm?: CSRFAlgorithm;
sessionId?: string;
}
interface CSRFVerifyOptions {
secret?: string;
encoding?: "base64" | "base64url" | "hex";
algorithm?: CSRFAlgorithm;
maxAge?: number;
sessionId?: string;
}
namespace Bun.CSRF {
function generate(secret?: string, options?: CSRFGenerateOptions): string;
function verify(token: string, options?: CSRFVerifyOptions): boolean;
}