.NET6 Blazor HttpClient Post等请求返回400 Bad Request如何解决
.NET 6 选用Individual身份验证模板创建的Blazor Hosted项目,默认全局开启了防伪令牌(CSRF Token)校验。GET请求不涉及服务端状态修改,不会触发该校验逻辑;而POST/PUT/DELETE/PATCH这类写操作请求,必须在请求头携带服务端下发的合法防伪令牌才能通过校验,你当前的客户端请求未携带对应令牌,因此直接返回400 Bad Request响应,响应头的Content-Type: application/problem+json就是框架校验失败返回的标准问题格式。
先做快速验证确认问题根源,不要上来就改一堆配置:
在服务端对应Post接口的Action上临时加[IgnoreAntiforgeryToken]特性跳过防伪校验:
[HttpPost] [IgnoreAntiforgeryToken] // 仅临时测试用,生产环境禁止保留 public async Task<ActionResult> PostSomethings(AppFolder args) { Console.WriteLine("Post Debug"+args); return await Task.Run(() => Ok()); }
重启项目重新发起请求,如果返回200 OK,就可以100%确认是防伪令牌校验导致的问题。
注意:这个方法仅用于本地测试定位问题,生产环境绝对不能全局跳过防伪校验,否则会存在严重的CSRF跨站请求伪造漏洞。
1. 服务端配置(Server项目)
第一步:注册防伪服务,指定前端传递令牌的请求头名称,在Program.cs的服务注册区域添加代码:
builder.Services.AddAntiforgery(options => { options.HeaderName = "X-CSRF-TOKEN"; });
第二步:添加令牌下发中间件,在用户首次访问应用时,将防伪令牌写入允许前端读取的Cookie中。中间件要放在app.UseAuthentication()之后、app.UseAuthorization()之前:
app.UseAuthentication(); // 新增防伪令牌下发中间件 app.Use(async (context, next) => { // 仅在访问应用根路径的GET请求时下发令牌 if (context.Request.Method == HttpMethods.Get && context.Request.Path == "/") { var antiforgery = context.RequestServices.GetRequiredService<IAntiforgery>(); var tokens = antiforgery.GetAndStoreTokens(context); // 必须设置HttpOnly=false,否则前端JS无法读取Cookie内容 context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken ?? string.Empty, new CookieOptions { HttpOnly = false, Secure = true, SameSite = SameSiteMode.Strict }); } await next(); }); app.UseAuthorization();
2. 客户端配置(Client项目)
第一步:自定义HTTP消息处理器,自动给非GET请求附加防伪令牌头。新建AntiforgeryHandler.cs类:
public class AntiforgeryHandler : DelegatingHandler { private readonly IJSRuntime _jsRuntime; public AntiforgeryHandler(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // GET请求不需要附加令牌 if (request.Method != HttpMethod.Get) { // 从Cookie中读取服务端下发的XSRF-TOKEN var token = await _jsRuntime.InvokeAsync<string>("eval", cancellationToken, "document.cookie.match(/(?:^|;)\\s*XSRF-TOKEN=([^;]+)/)?.[1] || ''"); if (!string.IsNullOrEmpty(token)) { request.Headers.TryAddWithoutValidation("X-CSRF-TOKEN", token); } } return await base.SendAsync(request, cancellationToken); } }
第二步:修改Program.cs中你之前注册的public命名HttpClient,绑定自定义消息处理器:
// 替换原有AddHttpClient的注册代码 builder.Services.AddHttpClient("public", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler<AntiforgeryHandler>(); builder.Services.AddTransient<AntiforgeryHandler>();
全部配置完成后重启项目,所有POST/PUT/DELETE/PATCH请求就可以正常通过校验,不再返回400错误。
如果做完上述配置还是返回400,检查两个点:
- 检查
AppFolder类是否存在公共无参构造函数,所有带[Required]特性的属性是否都传了值,避免模型校验失败返回400 - 检查请求路径是否正确,你当前代码里Post的路径是
WeatherForecast,确认控制器的路由前缀和Action路由是否匹配,避免路径不匹配触发400(路径问题一般返回404,概率极低)
内容的提问来源于stack exchange,提问作者รัฐพล.James

