目录
[ASP.NET Core 和 CSRF - 防伪造](#ASP.NET Core 和 CSRF - 防伪造)
[为 MVC 和 Razor Pages 添加防伪功能](#为 MVC 和 Razor Pages 添加防伪功能)
[使用 JavaScript 发送请求时应该怎么做?](#使用 JavaScript 发送请求时应该怎么做?)
[使用来自 cookie 的防伪令牌](#使用来自 cookie 的防伪令牌)
[Anti-Forgery tokens和 Duende 后端前端 (BFF)](#Anti-Forgery tokens和 Duende 后端前端 (BFF))
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。
在当今的 Web 应用程序中,安全性至关重要。Web 开发人员需要防范的常见攻击之一是跨站请求伪造 (CSRF)。ASP.NET Core 提供了内置支持,可通过防伪令牌来抵御此类攻击。
让我们来探讨一下 CSRF 的概念,了解 ASP.NET Core 中的默认设置,以及如何在 MVC、Razor Pages 和 Minimal API 中实现反伪造机制。我们还将介绍在使用源自 JavaScript 的 XHR 或 fetch 请求时如何处理反伪造令牌,以及负载均衡场景下的注意事项。
什么是跨站请求伪造(CSRF)?
跨站请求伪造 (CSRF)是一种攻击,恶意网站诱骗用户的浏览器在用户已登录的其他网站上执行未经授权的操作。这可能导致未经授权的操作,例如更改帐户详细信息、进行购物,甚至删除数据。

CSRF攻击示例
以下是 CSRF 攻击的工作原理示例:
- 用户登录其银行网站(example.com)并收到身份验证 cookie。
- 用户未退出登录就访问了恶意网站。
- 恶意网站使用用户的身份验证 cookie 向 example.com/transfer 提交一个隐藏表单。
- 银行在用户不知情或未经用户同意的情况下处理了转账。
为确保表单只能从合法网站生成的页面提交,您可以使用防伪令牌。
注意: 跨域资源共享 (CORS)是一项安全功能,它限制网页向与自身域名不同的域名发出 HTTP 请求。CORS 可以防止恶意网站读取应用程序的响应,但无法阻止 CSRF 攻击。即使采用严格的 CORS 策略,恶意网站仍然可以诱骗用户向您的应用程序提交表单。CORS 和 CSRF 防护都至关重要:CORS 基于来源控制资源访问,而 CSRF 则确保请求符合用户的意图。

ASP.NET Core 和 CSRF - 防伪造
ASP.NET Core 内置了对防伪令牌 (Anti-Forgery Tokens) 的支持,以帮助防止 CSRF 攻击。默认情况下,ASP.NET Core 会在表单中包含防伪令牌,并在服务器端对其进行验证。该框架会自动为 Razor Pages 和 MVC 应用程序生成并验证这些令牌。
当 ASP.NET Core 生成防伪令牌时,令牌的一部分(.AspNetCore.Antiforgery默认情况下)存储在 cookie 中,另一部分包含在表单或请求标头中。服务器随后会比较这两部分以验证请求的真实性。
生成的包含防伪令牌的 cookie 使用SameSiteMode.Strict和HttpOnly,这意味着浏览器中运行的 JavaScript 代码无法访问它,从而使恶意代码无法窃取 cookie 值。
请注意,ASP.NET Core 防伪令牌也绑定到当前用户。如果防伪令牌中嵌入的用户数据与已验证用户不匹配,则验证将失败。
一个例子可以说明这一点的重要性:当用户同时打开两个指向同一客户端应用程序的浏览器标签页时,Duende IdentityServer 会出现一个常见的支持问题。每个标签页都会要求用户使用 IdentityServer 登录。每个标签页都能成功渲染登录页面,并为匿名用户生成唯一的防伪令牌。请注意,由于两个标签页中的用户都尚未登录,因此他们的身份是未知的。用户随后在第一个标签页上成功登录,验证了第一个防伪令牌,登录成功!然而,第二个标签页将无法通过防伪验证,因为页面上的现有令牌与已验证用户的身份不再匹配。
添加防伪选项
在应用程序的设置代码中(通常位于 `<application.h>`)Program.cs,您可以配置防伪选项。该 AddAntiforgery方法有一个接受 lambda 表达式的重载版本,您可以在其中覆盖默认选项:
Cookie.Name- 设置用于存储防伪令牌的 cookie 名称。默认值:.AspNetCore.AntiforgeryFormFieldName- 设置用于存储防伪令牌的表单字段名称。默认值:__RequestVerificationTokenHeaderName- 设置用于存储防伪令牌的标头名称。默认值:nullSuppressXFrameOptionsHeader- 确定是否X-Frame-Options抑制标头。默认值:false
以下示例配置了 cookie 名称、表单字段名称和标头名称:
builder.Services.AddAntiforgery(options =>
{
options.Cookie.Name = "MyAntiforgeryCookie";
options.FormFieldName = "MyAntiforgeryField";
options.HeaderName = "X-CSRF-TOKEN";
});
默认选项通常适用于大多数应用程序,因此AddAntiforgery()通常只需简单调用而无需额外配置即可。
为 MVC 和 Razor Pages 添加防伪功能
使用防伪令牌时,需要确保令牌已渲染到要随请求一起发送的视图中,并且服务器可以验证该令牌。
在 MVC 或 Razor Pages 应用程序中,防伪令牌会自动包含在表单中 。FormTagHelper换句话说,<form>如果注册了此标签助手,添加该标签将自动渲染一个包含防伪信息的隐藏字段。
有些人不喜欢使用FormTagHelper这些标签助手,或者他们使用的是较旧版本的 ASP.NET,这些版本可能无法访问这些标签助手。为了确保将防伪令牌隐藏字段添加到每个表单中,您可以使用以下 @Html.AntiForgeryToken()方法手动添加:
<form method="post" action="/Home/Submit">
@Html.AntiForgeryToken()
<!-- form fields -->
</form>
在服务器端,Razor Pages 会自动验证防伪令牌。这得益于框架中一项会自动注册AutoValidateAntiforgeryTokenAttribute过滤器的约定。如果您希望在 ASP.NET MVC Core 中自动注册此过滤器,可以在启动时将其添加到过滤器集合中:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
在任何 ASP.NET MVC 控制器、操作方法或 Razor Pages 模型中,您都可以覆盖验证(或忽略)防伪令牌的默认行为。添加属性[RequireAntiForgeryToken]即可强制执行验证,或 [IgnoreAntiForgeryToken]跳过验证。
以下是如何[RequireAntiForgeryToken]向操作方法中添加内容:
public class CustomerController : Controller
{
RequireAntiforgeryToken
HttpPost
public IActionResult Save(Customer customer)
{
return View();
}
}
在 Razor Pages 中,可以将该属性添加到PageModel:
IgnoreAntiforgeryToken
public class Counter : PageModel
{
// ...
}
请注意,您无法将防伪属性应用于 Razor 页面中的单个处理程序。
为最小API添加防伪功能
在 ASP.NET Core Minimal API 中,您需要手动配置和验证防伪令牌。这涉及以下几个步骤:
- 注册所需服务
- 确保在每个响应中生成防伪令牌,并在每个请求中提交该令牌。
- 验证防伪代币
反伪造服务注册可通过以下方式进行Program.cs:
var builder = WebApplication.CreateBuilder(args);
// ...
builder.Services.AddAntiforgery();
// ...
注册后,IAntiforgery即可通过服务提供商使用该服务,并可用于获取或存储令牌。
如果您使用 HTML 表单向 ASP.NET Core Minimal API 发送请求,您可以添加一个名为 的隐藏表单字段 __RequestVerificationToken,并使用 方法的输出设置其值 Antiforgery.GetTokens(HttpContext).RequestToken。
IAntiforgery现在,在任何 API 中,您都可以通过注入并根据当前令牌验证来验证防伪令牌HttpContext:
app.MapPost("/save-data", async (HttpContext context, IAntiforgery antiforgery) =>
{
await antiforgery.ValidateRequestAsync(context);
// ...
return Results.Ok();
});

使用 JavaScript 发送请求时应该怎么做?
使用 JavaScript 调用带有防伪功能的现有 ASP.NET Core 端点时,调用代码必须包含一个令牌才能成功完成请求。例如,在使用 Angular、React 或 Vue 时,需要确保将防伪令牌作为请求参数POST或请求头发送。
通常情况下,您需要从隐藏的表单字段或 cookie 中检索防伪令牌,并将其包含在请求标头中。
使用隐藏表单字段中的防伪令牌
使用 MVC 或 Razor Pages 时,您可以使用@Html.AntiForgeryToken()以下方法生成隐藏的表单字段:
<form id="hiddenForm" method="post">
@Html.AntiForgeryToken()
</form>
使用 ASP.NET Core Minimal API,您可以生成一个表单,该表单添加一个名为 的隐藏字段__RequestVerificationToken,并使用 填充它Antiforgery.GetTokens(HttpContext).RequestToken。
在 JavaScript 中,您可以使用此隐藏字段的值并将其附加到请求标头(使用默认标头键RequestVerificationToken):
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
const formData = new FormData(...);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
fetch('/save-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': token
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
// Handle success
console.log('Success:', data);
})
.catch((error) => {
// Handle error
console.error('Error:', error);
});
使用来自 cookie 的防伪令牌
除了使用隐藏的表单字段在浏览器中呈现防伪令牌之外,您还可以将其放在 cookie 中,然后在稍后向服务器发送请求时使用该 cookie。
以下代码片段会为请求生成一个新的防伪令牌,并将其存储在名为 `<cookie_name>` 的 cookie 中XSRF-TOKEN:
var app = builder.Build();
app.Use(async (context, next) =>
{
var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>();
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false, SameSite = SameSiteMode.Strict });
await next();
});
// ...
请注意,此代码将 cookie 的HttpOnly属性设置为false,以确保在浏览器中运行的 JavaScript 代码可以读取存储的值。
在 JavaScript 中,您可以使用此 cookie 的值,并将其附加到请求标头(使用默认标头键RequestVerificationToken):
const token = document.cookie.match('(^|;)\\s*XSRF-TOKEN\\s*=\\s*([^;]+)')?.pop() || '';
fetch('/save-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': token
},
body: // ...
})
.then(response => response.json())
.then(data => {
// Handle success
console.log('Success:', data);
})
.catch((error) => {
// Handle error
console.error('Error:', error);
});
Anti-Forgery tokens和 Duende 后端前端 (BFF)
部署Duende Backend-for-Frontend (BFF)时,默认情况下会启用防伪造功能。BFF 中的防伪造实现方式与 ASP.NET Core 不同:它仅需要存在一个X-CSRF: 1 标头(可在设置中自定义)。任何 React、Angular、Vue、Blazor 或其他前端都可以轻松添加此类标头!
结合 BFF 的 cookie 要求,此标头会在浏览器中触发跨域请求的 CORS 预检请求。这会将调用者隔离到与后端相同的源,从而提供强大的 CSRF 防护。

负载均衡场景和数据保护
在负载均衡场景下,您必须确保所有应用程序实例中的防伪令牌保持一致。这需要配置数据保护以使用共享密钥存储机制,例如数据库、Azure Blob 存储或 Azure Key Vault。
数据保护示例配置Program.cs:
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\keys"))
.SetApplicationName("MyApp");
请注意,此处配置的应用程序名称也将用于防伪 cookie。
概括
防伪令牌是保护 ASP.NET Core 应用程序免受 CSRF 攻击的关键组成部分。通过了解默认设置以及如何在 MVC、Razor Pages 和 Minimal API 中配置和实现防伪令牌,您可以确保应用程序的安全。
此外,在动态 Web 应用程序中,处理 AJAX 请求中的防伪令牌对于维护安全性至关重要。在负载均衡场景下,配置数据保护以使用共享密钥存储机制是确保数据一致性的必要条件。
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。