ValidateAntiForgeryToken 返回 400 错误排查

.net ValidateAntiForgeryToken return bad request 错误的原因

你好![ValidateAntiForgeryToken] 返回 400 Bad Request 是一个非常常见的错误,它基本上意味着服务器端的 CSRF (Cross-Site Request Forgery, 跨站请求伪造) 保护机制验证失败了

简单来说,这个机制的工作原理是:

  1. 服务器生成一个令牌 (Token):当你访问一个包含表单的页面时,服务器会生成一个唯一的、加密的令牌。
  2. 令牌分两部分发送给客户端
    • 一部分放在 HTTP Cookie 中。
    • 另一部分放在页面表单的一个 隐藏字段 (<input type="hidden" name="__RequestVerificationToken" ...>) 中。
  3. 客户端提交表单:当你提交表单时(通常是 POST 请求),浏览器会同时发送 Cookie 和那个 隐藏字段的值
  4. 服务器验证:服务器上的 [ValidateAntiForgeryToken] 属性会检查收到的 Cookie 里的令牌和表单里的令牌是否存在,并且它们是否匹配。

如果验证失败,服务器就认为这个请求可能是伪造的、不安全的,于是拒绝处理,并返回一个 400 Bad Request 错误。


导致 400 Bad Request 错误的常见原因及解决方法

以下是最常见的原因,你可以逐一排查:

1. 表单中忘记添加 AntiForgeryToken

这是最常见的新手错误。如果你的 View (视图) 中的 <form> 标签内没有生成隐藏的令牌字段,那么提交时自然会验证失败。

解决方法:
确保在你的 Razor 视图的 <form> 标签内部添加了 @Html.AntiForgeryToken() (ASP.NET MVC) 或者 <input asp-for="__RequestVerificationToken" type="hidden" /> 的等效标签助手。

ASP.NET Core MVC / Razor Pages 示例:

<form asp-action="Create" method="post">
    @Html.AntiForgeryToken()
    
    <!-- 其他表单元素 -->
    <input type="text" name="productName" />
    <button type="submit">Submit</button>
</form>

或者使用 Tag Helper (更推荐在 ASP.NET Core 中使用):

<form asp-action="Create" method="post">
    <!-- Tag Helper 会自动为 POST 表单生成 AntiForgeryToken,通常无需手动添加 -->
    <!-- 但如果你的 form 标签没有 asp-action/asp-controller 等,就需要手动确认 -->
    
    <!-- 其他表单元素 -->
    <input type="text" name="productName" />
    <button type="submit">Submit</button>
</form>

注意:在 ASP.NET Core 中,如果 <form> 标签使用了 method="post"asp-actionasp-controller 等 Tag Helper,框架会自动为你生成 AntiForgeryToken。但如果没有使用这些 Tag Helper,则需要手动添加 @Html.AntiForgeryToken()

2. 使用 AJAX (如 fetch, jQuery.ajax) 提交数据

AJAX 请求不会自动提交表单中的隐藏字段。你需要手动读取令牌的值,并将其添加到请求头 (Request Header) 或请求体 (Request Body) 中。

解决方法:
在页面上先获取令牌的值,然后在 AJAX 请求中将其作为请求头发送。

Step 1: 在你的 Razor 视图中,将令牌放在一个容易被 JavaScript 获取的地方,比如一个隐藏输入框或 meta 标签。
@Html.AntiForgeryToken() 已经帮你做了这件事,你只需要用 JS 去读取它。

Step 2: 在 JavaScript 中读取令牌并添加到请求头。

使用 fetch API 示例:

// 获取隐藏字段中的令牌值
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;

fetch('/YourController/YourAction', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'RequestVerificationToken': token // 将令牌添加到请求头
    },
    body: JSON.stringify({ name: 'test' })
})
.then(response => {
    if (!response.ok) {
        // 请求失败
        console.error('Request failed with status:', response.status);
    }
    return response.json();
})
.then(data => {
    console.log('Success:', data);
});

使用 jQuery $.ajax 示例:

var token = $('input[name="__RequestVerificationToken"]').val();

$.ajax({
    url: '/YourController/YourAction',
    type: 'POST',
    data: {
        name: 'test'
        // 注意:如果以 application/x-www-form-urlencoded 方式提交,
        // 并且 __RequestVerificationToken 在 data 中,也可以
        // __RequestVerificationToken: token 
    },
    headers: {
        'RequestVerificationToken': token // 推荐的方式是放在 Header 中
    },
    success: function (result) {
        console.log('Success:', result);
    },
    error: function (xhr, status, error) {
        console.error('Error:', xhr.status, error);
    }
});

3. Cookie 丢失或被禁用

如果用户的浏览器禁用了 Cookie,或者 anti-forgery cookie(通常名为 .AspNetCore.Antiforgery.*)因某种原因丢失(例如,用户清除了浏览器数据,或者 Cookie 过期),验证也会失败。

解决方法:
这通常是用户端的问题,但你可以确保你的网站正确设置了 Cookie 策略,尤其是在涉及 iframe 或跨域场景时,要关注 SameSite 属性。

4. 页面缓存问题

如果你的页面被过度缓存(例如,通过代理服务器或浏览器),用户可能会得到一个包含旧的、已失效令牌的页面。当他们提交表单时,这个旧令牌与服务器期望的新令牌不匹配。

解决方法:
对于包含表单的页面,应禁用或谨慎使用输出缓存。可以在 Action 上添加 [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] 来防止页面被缓存。

5. Web Farm / 负载均衡环境配置问题

如果你将应用部署在多台服务器(Web Farm)上,并且没有配置共享的密钥环 (Data Protection Key Ring),那么问题就会出现:

  • 服务器 A 生成了一个令牌并发送给用户。
  • 用户的下一个请求被负载均衡器分配给了服务器 B。
  • 服务器 B 使用自己的密钥无法解密由服务器 A 生成的令牌,导致验证失败。

解决方法:
你需要配置数据保护 (Data Protection) 系统,让所有服务器共享同一个密钥环。这通常通过将密钥存储在共享位置(如 Azure Blob Storage, Redis, 或网络共享文件夹)来实现。

ASP.NET Core 配置示例 (Program.csStartup.cs):

using Microsoft.AspNetCore.DataProtection;

// ...

var builder = WebApplication.CreateBuilder(args);

// 将密钥存储在共享网络文件夹中
builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\keys\"))
    .SetApplicationName("YourAppName"); // 确保所有应用实例名称相同

// ...

快速排查清单

  1. 检查视图:确保你的 <form> 中有 @Html.AntiForgeryToken()。右键查看页面源代码,搜索 __RequestVerificationToken,看是否存在这个隐藏字段。
  2. 检查网络请求:打开浏览器开发者工具 (F12),切换到 “Network” (网络) 标签。
    • 查看表单提交:找到你的 POST 请求,在 “Payload” 或 “Form Data” 中查看 __RequestVerificationToken 是否有值。
    • 查看 Cookie:在请求的 “Headers” → “Request Headers” 中,或 “Application” (应用) → “Cookies” 中,查找是否存在 anti-forgery cookie。
  3. 检查 AJAX 调用:如果你用的是 AJAX,确认你已经将令牌值正确地添加到了请求头中。在开发者工具的网络请求详情里检查 RequestVerificationToken 请求头是否存在且有值。
  4. 检查部署环境:如果你在生产环境遇到此问题,但在本地开发时没问题,优先怀疑是不是 Web Farm/负载均衡配置问题。

通过以上步骤,你应该能很快定位并解决 [ValidateAntiForgeryToken] 导致的 400 错误。