剃刀页面和HTTP 400错误中的Ajax帖子

似乎每隔一天左右,一个新的问题发布给了一个社区 覆盖ASP.NET核心剃刀页面的论坛询问为什么Ajax POST没有 似乎工作,或导致HTTP 400错误。本文解释了最常见的原因,以及 提供有关如何解决的指导。

诊断Ajax请求问题的第一步是实际检查 请求本身。有许多工具可用于此,但最多 可访问的工具是主要浏览器提供的开发人员工具。  They are 通过在查看时按F12在Chrome中,IE,Edge和Firefox可访问 您的网页或Ctrl + Shift + I在Opera中。后者的关键组合也适用于 Firefox。在所有情况下,面板应在看起来的浏览器中打开 like this:

浏览器开发人员工具

突出的元素是 网络 标签,目前打开并显示浏览器网络流量的详细信息,以及 全部 默认选择的选项。此选项会导致所有网络流量记录和报告。坐到右侧的选项 - XHR. - 除了由XMLHTTPREQUEST对象或浏览器的获取API发起的所有请求之外,筛选出所有请求。换句话说,如果选择XHR选项,则只能看到AJAX请求的详细信息。

一旦您打开了此面板,您就会更好的故障位置 找。和第一个潜在的失败点排除掉子是否是ajax 请求实际上是全部制定的。如果网格中没有任何内容,则 请求没有。这可能是以任何数量的原因。如果有 JavaScript代码中的错误,将出现Inidcator(箭头,显示Chrome然后Edge),并将显示在控制台选项卡中。

浏览器
浏览器

如果没有报告错误,但仍然没有提出要求 响应触发器操作,检查启动事件处理程序是否是启动的事件处理程序 该请求正确地连接到触发元素。如果请求, details will appear, hopefully accompanied by a nice 200 HTTP status code to 表示一切顺利。但是,在这个标题中暗示 article, the status code that causes the most confusion is 400:

http 400.

根据这一点 http标准:

400(错误请求)状态代码指示服务器不能或 由于被认为是的东西,不会处理请求 客户端错误(例如,格式错误的请求语法,无效请求 消息框架或欺骗请求路由)。

So there is an error in the way that the post request has been constructed, but what is it? The most common cause for a 400 Bad Request Razor页面中的状态代码是缺少请求验证令牌。

请求验证令牌

请求验证是一种可确保的过程 post 您的应用程序收到的请求源自应用程序,而不是 来自第三方。请求验证是军械库中的重要工具 against 跨站点请求伪造(CSRF)攻击,并在razor页面中默认启用。 一个名为的隐藏的表格字段 __RequestVerificationToken 在每个形式内呈现 表格标签助手。它包含加密的令牌。与表单请求发送的cookie中包含相同的值。两者的存在 当ASP.NET核心处理POST请求时验证令牌及其值。如果验证失败,则框架返回a 400 status code.

在正常表单提交期间,包含CSRF令牌的隐藏字段自动包含在内 有效载荷。导致失败的ajax帖子的最常见原因 400 status code are:

  • CSRF令牌生成但是 不包括在内 posted payload
  • CSRF令牌被包含在帖子中,但在某种程度上 这阻止了在服务器上发现。
  • 没有生成CSRF令牌,因为未使用表单标签助手

没有生成隐藏的字段

您需要生成CSRF令牌值。虽然表格标签助手是最多的 将令牌值作为隐藏字段生成令牌值的常见方法,这不是唯一的 方法。事实上,它甚至可能没有意义生成表单标签或隐藏 字段为您的场景 - 截然不同,如果您未发布表单字段值。 如果在页面中包含表单确实有意义,那么这样做,但确保 that the 方法 attribute is set to post. CSRF tokens are not generated for forms that use the get method.

If you don't need a form, you can inject the IAntiforgery interface into your 页面,并使用它来生成令牌值:

@page
@model MyApp.Pages.IndexModel
@inject IAntiforgery antiforgery
@{
    var token = antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
}

You will need to either use the fully qualified namespace (Microsoft.AspNetCore.Antiforgery) to reference IAntiforgery, or add a using statement to the _ViewImports 文件。然后可以在AJAJX帖子中使用令牌。

包括帖子中的CSRF令牌

您可以以多种方式构建Ajax帖子的数据。 如果你用jquery,它 连载() method 将注意确保所有指定的表单值都是 正确编码提交。使用fetch API时,可以构建一个 FormData 来自相关表单元素的对象,也将 确保在有效载荷中包含所有字段,包括隐藏字段。你 may be doing this, but still your posts generate 400 codes. If so, 检查以确保您不会严格串行序列化表单值 JSON (using e.g. JSON.stringify) unnecessarily as this will prevent 服务器发现的令牌。

如果您自己构建值,很容易忽略验证 token.

验证令牌可以用两种方式在帖子中手动包含,作为一部分 请求正文,或在标题中。 默认情况下,必须命名标题 RequestVerificationToken and the form field's name must be __RequestVerificationToken, although you can customise them through configuration via the AntiforgeryOptions in ConfigureServices e.g.

services.AddAntiforgery(o => o.HeaderName = "CSRF-TOKEN");

对OWASP,通过JavaScript在HTTP请求标题中插入CSRF令牌,而不是在隐藏的字段表单参数中添加令牌更安全。 If you are using jQuery, you can use the $.ajax 方法 instead of $.post,因为它使您可以访问更多选项,包括设置标题:

$.ajax({
    type: "POST",
    url: "/",
    data: { foo: "bar" },
    headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() },
    success: function (response) {
        ...

以下是您可以使用fetch完成同一件事的方式:

var token = document.querySelector('input[name="__RequestVerificationToken"]').getAttribute("value");
fetch("", {
    method: "post",
    headers: {
        "RequestVerificationToken": token
    },
    body: { foo: "bar" }
}).then(() => {
    ...
});

如果将CSRF令牌包含为表单字段和标题值,则标题 价值优先。如果标题值失败验证,系统将 not fall back to the form field. It will simpy return a BadRequestResult, generating a 400 response code. 

概括

本文提供了一些关于故障排除失败的基本指导 发布请求,然后专注于ASP.NET核心剃须刀中最常见的罪魁祸首 页面。它解释了CSRF令牌是什么,如何由剃刀页面生成, and how you can generate one using the IAntiforgery API. It also 解释了如何在Ajax帖子中手动包括令牌,以便它可以 discovered.