.net Core MVC:不接受 X-SRF-TOKEN,返回 400

Posted

技术标签:

【中文标题】.net Core MVC:不接受 X-SRF-TOKEN,返回 400【英文标题】:.net Core MVC: X-SRF-TOKEN not accepted, 400 returned 【发布时间】:2018-05-21 22:41:16 【问题描述】:

我有一个使用 angularJS 的 .net 核心应用程序,我想保护受我们基于 cookie 的身份验证保护的 api 调用。我按照本文中的步骤操作:

https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery

我将services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); 添加到我的服务配置中

加载页面时,我在开发人员工具中看到了XSRF-TOKEN cookie。

我看到 X-XSRF-TOKEN 标头被添加到我的 $http 发送请求中。

我已将 [AutoValidateAntiforgeryToken] 添加到处理 ajax 请求的控制器中。

我已启用 ssl,并且正在通过 https 访问页面。

我可以按预期通过这个 api 端点进行 GET 请求。但是,我收到 400 错误,但没有详细说明为什么请求在 PUT 和 POST 上不正确。

我知道X-XSRF-TOKEN 正在请求中,(在 chrome 开发工具的网络选项卡中看到)所以我不确定我缺少什么以允许正确接收这些请求。

TL;DR: 为什么 .net 核心拒绝我的有效 AntiforgeryToken?

更新 尝试了@joey 建议的解决方案,但没有奏效,仍然收到 400 条回复。下面的代码反映了我尝试解决此问题的另一种解决方案(也称为 angularjs 为跨站点脚本保护设置默认 cookie 和标头的解决方案)

我还尝试配置 AngularJS 以更改 cookie 和标头名称与我在 Startup.cs 中配置的内容相匹配。我更改了他们的名字以尝试XSRF-TOKEN(cookie)和X-XSRF-TOKEN(标题)以及CSRF-TOKENX-CSRF-TOKEN,虽然角度内的配置正确使用我提供的新默认值,但我的.net 核心中的身份验证代码仍然无法正常工作。

这里是我如何配置 AngularJS 的更多信息:

app.config(function ($httpProvider) 
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
    $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';
);

这是我在Startup.cs 文件中的ConfigureServices 行:

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

最后是我添加到Startup.cs 文件的Configure 方法中的代码:

    app.Use(next => context => 
    
        string path = context.Request.Path.Value;
        if (path.Contains("/MyProtectedPath/"))
        
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,
                new CookieOptions  HttpOnly = false );
        
        return next(context);
    );

更新 2: 我已将以下内容添加到我的控制器操作中:

if (HttpContext.Request.Method.ToLower(CultureInfo.InvariantCulture) != "get")

    await _antiforgery.ValidateRequestAsync(HttpContext);

AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user.

我想antiforgery.GetAndStoreTokens(context) 可能会覆盖在页面加载时发送的当前 cookie,所以我让它只在 GET 请求上命中该代码,但我得到相同的帖子结果。

更新 3 感谢@joey 的帮助,我查看了我的 startup.cs 文件,并且我在单独的分支中有 UseAuthentication:

    app.Use(next => context => //note: order of these use statements is key. keep this antiforgery section above auth!
    
        string path = context.Request.Path.Value;
        if (path.ToLower(CultureInfo.InvariantCulture).Contains("/campaigns/proxy/"))
        
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                new CookieOptions  HttpOnly = false );
        
        return next(context);
    );

    // Use Basic Auth for /api
    app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
    
        branch.UseBasicAuth();
    );
    // Use Identity for everything except /api
    app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
    

        app.UseAuthentication(); // required for .net core 2.0 authentication
    );

我不确定为什么此时我的令牌会被视为无效...

【问题讨论】:

我在文章中注意到,它提到了 webapi 控制器,而不是我目前使用的 MVC 控制器。这会有所作为吗? 那么你解决了这个问题吗? 不幸的是没有。我没时间让它工作了。 伙计,这让我发疯了,我一直在跟踪文档的每一个细节,我一直在应用我在搜索过程中遇到的许多其他方法,但没有任何帮助。它变得令人沮丧,我在 Github 上打开一个问题 app.UseAuthentication();没有使用分支 【参考方案1】:

在您发布的代码中,您添加了标题X-XSRF-TOKEN,但标题应为X-CSRF-TOKEN

您链接的网页中的示例提供了示例:

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

更新:

感谢您使用附加代码和信息进行澄清。此处选择的实现 CSRF 的方法是将令牌作为标头传递给初始 html 文件的响应。以下是如何为 SPA Web 应用程序配置这种情况的示例:

app.Use(next => context =>

    string path = context.Request.Path.Value;
    if (
        string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) || 
        string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)
    )
    
        var tokens = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken, 
            new CookieOptions()  HttpOnly = false );
    

    return next(context);
);

但是,如果您的服务配置为使用 String.Contains 方法 [1],则在任何位置包含 /MyProtectedPath/ 的任何路径都会调用 antiforgery.GetAndStoreTokens(context)。这意味着它的配置方式不仅匹配/MyProtectedPath/,还匹配/MyProtectectedPath/a/b/c/a/b/c/MyProtectedPath/

检查后续请求中发送的CSRF令牌适用的HTTP方法如下:

if (string.Equals("POST", context.Request.Method, StringComparison.OrdinalIgnoreCase))

    await antiforgery.ValidateRequestAsync(context);
    // The line above will throw if the CSRF token is invalid.

如果在此之前为任何匹配路径调用方法GetAndStoreTokens,则令牌将在检查之前被覆盖,这就是为什么.net示例通常会首先订购GetAndStoreTokens,但要查看特定条件路径和 HTTP 方法。

[1]https://msdn.microsoft.com/en-us/library/dy85x1sa(v=vs.110).aspx

【讨论】:

因此,在上面链接自 Microsoft 的文章中,我可以使用角度默认 X-XSRF-TOKEN 覆盖默认标题名称。这个覆盖默认标题名称的功能是否损坏? @NathanTregillus 感谢您使用附加配置代码进行更新,这很有帮助!返回 400 状态码的 HTTP 请求是否嵌套在路径 /MyProtectedPath/ 下? 正确。方法本身没有达到。如果我关闭该属性,呼叫就会正确进行。 嘿@Joey,只是想澄清一下,我应该在获取请求时使用antiforgery.GetAndStoreTokens(context);,对于非获取请求使用antiforgery.ValidateRequestAsync(context);,对吗? @NathanTregillus 是的,通常不需要为 GET 请求调用 antiforgery.ValidateRequestAsync(context)AutoValidateAntiforgeryToken 是 ASP.NET MVC 过滤器之一,因此它适用于使用 ASP.NET MVC 创建的表单(它将在每个 HTML 中添加类似于 <input name="__RequestVerificationToken" type="hidden" value="..." /> 的内容)。由于您使用的是 Angularjs,我假设表单不是使用 ASP.NET MVC 创建的,而是来自静态资产(带有捆绑 javascript、css 等的 HTML)【参考方案2】:

这是对我有用的配置, 我启用了CORS

services.AddCors(options =>
            
                options.AddPolicy("CorsPolicy",
                    builder => builder
                        .WithOrigins("https://www.artngcore.com:4200") //Note:  The URL must be specified without a trailing slash (/).
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
            );

然后在启动时将以下内容添加到您的服务中,

services.AddAntiforgery(options =>
                
                    options.HeaderName = "X-XSRF-TOKEN";
                    options.SuppressXFrameOptionsHeader = false;
                );    

和中间件,

            app.UseAuthentication();
                app.Use(next => context =>
                   
                       string path = context.Request.Path.Value;
                       var tokens = antiforgery.GetAndStoreTokens(context);
                       context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                            new CookieOptions()  HttpOnly = false, 
Secure = true // set to false if not using SSL );
                       return next(context);
                   );

在控制器中,

[Route("/api/[controller]/[action]")]
[EnableCors("CorsPolicy")]
[Authorize]
public class AccountController : Controller ....

如下配置你的标题,

const headers = new HttpHeaders(
    'Content-Type': 'application/json',
    'X-XSRF-TOKEN': `$this.cookieService.get('XSRF-TOKEN')`
);

最后,诀窍是登录(身份验证)后必须刷新令牌,我所做的只是调用API路径来调用令牌进行刷新, 即登录后调用此API,

    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> ContactInitialization()
    
        await Task.Delay(1);

        return StatusCode(200);

    

希望对你有帮助,

【讨论】:

在客户端手动实现 X-XSRF-TOKEN 标头为我解决了这个问题。当 Angular 应用托管在与其调用的 API 应用不同的站点上时,Angular 的内置 XSRF 缓解似乎无法正常工作。【参考方案3】:

乔伊的回答似乎是对的。但是,既然您提到它对您不起作用,我必须这样做才能使其正常工作。

首先,from the docs:

AngularJS 使用约定来处理CSRF。如果服务器发送一个名为 XSRF-TOKEN 的 cookie,Angular $http 服务将在向该服务器发送请求时将此 cookie 中的值添加到标头中。这个过程是自动的;您不需要明确设置标题。标头名称为X-XSRF-TOKEN。服务器应检测此标头并验证其内容。

添加防伪服务

在调用AddMvc()后将Antiforgery服务添加到Startup.ConfigureServices()中的服务集合中:

services.AddAntiforgery(options => 
    options.HeaderName = "X-XSRF-TOKEN";
);

我们基本上是在告诉 ASP.NET 在验证 xsrf 令牌时查找 X-XSRF-TOKEN 标头。

发送带有令牌的 cookie

现在,对于来自 SPA 的每个请求,我们都需要发送带有令牌值的 XSRF-TOKEN cookie。我们可以通过一个快速的中间件来做到这一点:

// TODO: Refactor this to a separate middleware class
app.Use(next => context =>

    // TODO: Add if conditions to ensure the cookies
    // are only sent to our trusted domains

    // Send the token as a javascript readable token
    var tokens = antiforgery.GetAndStoreTokens(context);
    context.Response.Cookies.Append(
        "XSRF-TOKEN", 
        tokens.RequestToken, 
        new CookieOptions()  HttpOnly = false 
    );

    return next(context);
);

验证您的操作

验证令牌的最简单方法是添加[ValidateAntiForgeryToken] 属性。更好的选择是将 MVC 配置为将 AutoValidateAntiforgeryToken 全局应用于所有操作,并在 Startup.ConfigureServices() 中使用以下内容:

services.AddMvc(options => 
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));

Read the docs on token validation.

【讨论】:

以上是关于.net Core MVC:不接受 X-SRF-TOKEN,返回 400的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.net Core MVC 2.1 中创建文本文件并下载而不保存在服务器上

在 ASP.NET Core 3 MVC Web 应用程序中设置路由

如何将我自己的类注入到 ASP.NET MVC Core 的控制器中?

在 ASP.NET MVC 5 控制器中使用 POST 从 dotnet Core Web API 下载文件

ASP.NET MVC Core WebAPI 项目不返回 html

ASP.Net Core MVC 依赖注入不起作用