我有一个ASP.NET Core 3.1的后台,前台是angular 9(基于dotnet angular模板,只是更新了angular到v9).我使用cookie认证(我知道JWT更适合SPA,就当是实验吧),我还在服务器端增加了对CSRF保护的支持。
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN"; // angular csrf header name
});
我在服务器端设置了自动检查CSRF的功能,使用的是
options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())
所以GET请求不会被CSRF检查,但POST会。
在最开始的时候,angular应用会向以下地方发出GET请求 api/init
来获取一些启动前的初始数据。在服务器端,这个动作初始化CSRF如下。
// init action body
var tokens = _antiForgery.GetAndStoreTokens(HttpContext);
Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions
{
HttpOnly = false
});
// return some inital data DTO
这和预期的一样--GET响应包含2个CSRF cookie--第一个是ASP .NET core dafault CSRF cookie。.AspNetCore.Antiforgery...
二是 XSRF-TOKEN
角度将读取并放入 X-XSRF-TOKEN
头,用于后续请求。
如果之后我真的登录了(POST请求中包含了对 api/auth/login
),一切都正常了--请求被POSTed,包括 X-XSRF-TOKEN
头和CSRF验证通过,所以如果凭证正确,用户就可以登录。
现在问题就从这里开始了。ASP .NET服务器应用程序使用了这里描述的无身份认证的cookie认证。https:/docs.microsoft.comen-usaspnetcoresecurityauthenticationcookie?view=aspnetcore-3.1。. 在登录操作中,CSRF令牌也需要重新生成,因为在认证时,CSRF令牌开始包含认证用户的身份。因此,我的登录动作是这样的。
public async Task<IActionResult> Login(CredentialsDto credentials)
{
// fake user credentials check
if (credentials.Login != "admin" || credentials.Password != "admin")
{
return Unauthorized();
}
var claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "theAdmin"),
}, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync(claimsPrincipal);
// refresh antiforgery token on login (same code as in init action before)
var tokens = _antiForgery.GetAndStoreTokens(HttpContext);
Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions
{
HttpOnly = false
});
return new JsonResult(new UserDto { Id = 1, Login = "theAdmin" });
}
这个动作不工作. 响应包含以下内容 XSRF-TOKEN
cookie,但随后的POST请求(在我的例子中,它的logout = POST到 api/auth/logout
)以400失败,尽管angular正确地将这个cookie值放到了 X-XSRF-TOKEN
我相信原因是dafault的 "头像"。.AspNetCore.Antiforgery...
由于某种原因,cookie在响应中没有被设置,因此即使在登录后也保留了原始值,因此CSRF检查失败,因为值不匹配。
这种情况下,如何正确刷新CSRF令牌?
又做了一些试错,也搜索了ASP .NET github,找到了 https:/github.comdotnetaspnetcoreissues2783。 和 https:/github.comaspnetAntiforgeryissues155。但似乎我想实现的目标无法在单个请求中实现,因为在单个请求处理过程中,用户的身份不会改变(设计上的pearentyl)。
唯一的办法是在登录退出后再进行额外的请求,这样CSRF token就会根据新获得的身份正确刷新。
我是通过我的登录动作返回一个302重定向来实现的,当遵循这个重定向时,就会执行CSRF刷新。注销时也需要这样做。
副作用是请求量增加了一倍,而且可能会出现Angular跟随重定向的问题(我在Chrome浏览器中使用Angular 9的时候就能用,但是网上有很多关于Angular的HttpClient不跟随重定向的抱怨)。另外,当登录请求成功,但由于某种原因跟随重定向到token刷新失败时,你可能最终会陷入一个limbo状态--那么你就会被过时的CSRF token所困。所以,不是很好,也不是很糟糕...