.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-TOKEN
和X-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 下载文件