我正在构建单页应用程序并遇到防伪令牌问题。
我知道为什么问题发生我只是不知道如何修复它。
发生以下情况时我收到错误:
防伪标记适用于用户“”,但当前用户是 “用户名”
发生这种情况的原因是因为我的应用程序是100%单页,并且当用户通过ajax帖子成功登录到/Account/JsonLogin
时,我只是通过返回“已验证的视图”来切换当前视图从服务器但不重新加载页面。
我知道这就是原因,因为如果我在步骤3和4之间简单地重新加载页面,就没有错误。
因此,在重新加载页面之前,加载表单中的@Html.AntiForgeryToken()
似乎仍会为旧用户返回一个标记。
如何更改@Html.AntiForgeryToken()
以便为经过身份验证的新用户返回令牌?
我在GenericalPrincipal
每个IIdentity
注入了一个新的Application_AuthenticateRequest
,因此当@Html.AntiForgeryToken()
被称为HttpContext.Current.User.Identity
时,实际上是我的自定义身份将IsAuthenticated
属性设置为true,但@Html.AntiForgeryToken
似乎仍为旧用户呈现令牌,除非我重新加载页面。
答案 0 :(得分:163)
这种情况正在发生,因为防伪令牌将用户的用户名作为加密令牌的一部分嵌入,以便更好地进行验证。当您第一次调用@Html.AntiForgeryToken()
时,用户未登录,因此令牌将具有用户名的空字符串,在用户登录后,如果您不替换防伪令牌,则不会通过验证,因为初始令牌是匿名用户,现在我们有一个已知身份验证用户。
您有几个选项可以解决此问题:
就在这个时候让你的SPA做一个完整的POST,当页面重新加载时,它会有一个防伪标记,其中嵌入了更新的用户名。
只有@Html.AntiForgeryToken()
的部分视图,登录后立即执行,执行另一个AJAX请求,并将现有的防伪标记替换为请求的响应。
只需禁用防伪验证执行的身份检查即可。将以下内容添加到 Application_Start 方法中: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true
。
答案 1 :(得分:20)
要修复错误,您需要将OutputCache
数据注释放在登录页面的获取ActionResult
上:
[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")]
public ActionResult Login(string returnUrl)
答案 2 :(得分:9)
我的应用程序发生了很多次,所以我决定谷歌吧!
我找到了一个关于这个错误的简单解释! 用户双击登录按钮!您可以在下面的链接中看到另一位用户正在谈论它:
MVC 4 provided anti-forgery token was meant for user "" but the current user is "user"
我希望它有所帮助! =)
答案 3 :(得分:6)
我遇到了同样的问题,这个肮脏的黑客修复了它,至少在我能以更清洁的方式修复它之前。
public ActionResult Login(string returnUrl)
{
if (AuthenticationManager.User.Identity.IsAuthenticated)
{
AuthenticationManager.SignOut();
return RedirectToAction("Login");
}
...
答案 4 :(得分:1)
我在注册过程中遇到了一个相当具体但类似的问题。一旦用户点击发送给他们的电子邮件链接,他们就会登录并直接发送到帐户详细信息屏幕以填写更多信息。我的代码是:
Dim result = Await UserManager.ConfirmEmailAsync(userId, code)
If result.Succeeded Then
Dim appUser = Await UserManager.FindByIdAsync(userId)
If appUser IsNot Nothing Then
Dim signInStatus = Await SignInManager.PasswordSignInAsync(appUser.Email, password, True, shouldLockout:=False)
If signInStatus = SignInStatus.Success Then
Dim identity = Await UserManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ApplicationCookie)
AuthenticationManager.SignIn(New AuthenticationProperties With {.IsPersistent = True}, identity)
Return View("AccountDetails")
End If
End If
End If
我发现Return View(“AccountDetails”)给了我令牌异常,我猜是因为ConfirmEmail函数是用AllowAnonymous装饰的,但AccountDetails函数有ValidateAntiForgeryToken。
更改返回的返回值RedirectToAction(“AccountDetails”)为我解决了这个问题。
答案 5 :(得分:1)
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]
public ActionResult Login(string returnUrl)
您可以通过在Login(Get)操作的第一行放置一个断点来测试这一点。在添加OutputCache指令之前,断点将在第一次加载时被点击,但是在单击浏览器后退按钮之后它不会。添加指令后,您最终应该每次都会遇到断点,因此AntiForgeryToken将是核心的,而不是空的。
答案 6 :(得分:1)
我在生产服务器上大多数时间都遇到相同的例外情况。
为什么会发生?
当用户使用有效凭据登录并登录并重定向到另一个页面时,会发生这种情况。当他们按下“返回”按钮后,将显示登录页面,然后他再次输入有效凭据,此时将发生此异常。
如何解决?
只需添加此行即可完美运行,没有错误。
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
答案 7 :(得分:0)
我在单页ASP.NET MVC Core应用程序中遇到了同样的问题。我通过在所有更改当前身份声明的控制器操作中设置HttpContext.User
来解决此问题(因为MVC仅针对后续请求执行此操作,如所讨论的here)。我使用结果过滤器而不是中间件将反伪造cookie附加到我的响应中,这确保它们仅在MVC操作返回后生成。
控制器(注意:我使用ASP.NET核心身份管理用户):
[Authorize]
[ValidateAntiForgeryToken]
public class AccountController : Controller
{
private SignInManager<IdentityUser> signInManager;
private UserManager<IdentityUser> userManager;
private IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory;
public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
{
this.signInManager = signInManager;
this.userManager = userManager;
this.userClaimsPrincipalFactory = userClaimsPrincipalFactory;
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(string username, string password)
{
if (username == null || password == null)
{
return BadRequest(); // Alias of 400 response
}
var result = await signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
var user = await userManager.FindByNameAsync(username);
// Must manually set the HttpContext user claims to those of the logged
// in user. Otherwise MVC will still include a XSRF token for the "null"
// user and token validation will fail. (MVC appends the correct token for
// all subsequent reponses but this isn't good enough for a single page
// app.)
var principal = await userClaimsPrincipalFactory.CreateAsync(user);
HttpContext.User = principal;
return Json(new { username = user.UserName });
}
else
{
return Unauthorized();
}
}
[HttpPost]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
// Removing identity claims manually from the HttpContext (same reason
// as why we add them manually in the "login" action).
HttpContext.User = null;
return Json(new { result = "success" });
}
}
附加防伪cookie的结果过滤器:
public class XSRFCookieFilter : IResultFilter
{
IAntiforgery antiforgery;
public XSRFCookieFilter(IAntiforgery antiforgery)
{
this.antiforgery = antiforgery;
}
public void OnResultExecuting(ResultExecutingContext context)
{
var HttpContext = context.HttpContext;
AntiforgeryTokenSet tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
HttpContext.Response.Cookies.Append(
"MyXSRFFieldTokenCookieName",
tokenSet.RequestToken,
new CookieOptions() {
// Cookie needs to be accessible to Javascript so we
// can append it to request headers in the browser
HttpOnly = false
}
);
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
Startup.cs extract:
public partial class Startup
{
public Startup(IHostingEnvironment env)
{
//...
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddAntiforgery(options =>
{
options.HeaderName = "MyXSRFFieldTokenHeaderName";
});
services.AddMvc(options =>
{
options.Filters.Add(typeof(XSRFCookieFilter));
});
services.AddScoped<XSRFCookieFilter>();
//...
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
//...
}
}
答案 8 :(得分:0)
当您已通过身份验证登录时,会显示该消息。
此助手与[ValidateAntiForgeryToken]
属性完全相同。
System.Web.Helpers.AntiForgery.Validate()
从控制器中删除[ValidateAntiForgeryToken]
attribut并将此帮助程序置于操作方法中。
因此,当用户已经过身份验证时,重定向到主页,或者如果在此验证后没有继续验证有效的防伪令牌,那么。
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
System.Web.Helpers.AntiForgery.Validate();
要尝试重现错误,请按以下步骤操作: 如果您在登录页面上并且未经过身份验证。如果复制选项卡,则使用第二个选项卡登录。 如果你回到登录页面上的第一个标签,你尝试登录而不重新加载页面......你有这个错误。
答案 9 :(得分:-2)
在互联网商店中存在防伪标记验证问题:用户打开许多标签(带有商品),并在登录后尝试登录另一个并获得此类AntiForgeryException。 所以,AntiForgeryConfig.SuppressIdentityHeuristicChecks = true对我没有帮助,所以我使用了这样丑陋的hackfix,也许它会对某人有所帮助:
public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext exceptionContext)
{
var exception = exceptionContext.Exception;
var request = HttpContext.Current.Request;
if (request != null)
{
if (exception is HttpAntiForgeryException &&
exception.Message.ToLower().StartsWith("the provided anti-forgery token was meant for user \"\", but the current user is"))
{
var isAjaxCall = string.Equals("XMLHttpRequest", request.Headers["x-requested-with"], StringComparison.OrdinalIgnoreCase);
var returnUrl = !string.IsNullOrWhiteSpace(request["returnUrl"]) ? request["returnUrl"] : "/";
var response = HttpContext.Current.Response;
if (isAjaxCall)
{
response.Clear();
response.StatusCode = 200;
response.ContentType = "application/json; charset=utf-8";
response.Write(JsonConvert.SerializeObject(new { success = 1, returnUrl = returnUrl }));
response.End();
}
else
{
response.StatusCode = 200;
response.Redirect(returnUrl);
}
}
}
ExceptionHandler.HandleException(exception);
}
}
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ExceptionPublisherExceptionFilter());
filters.Add(new HandleErrorAttribute());
}
}
如果可以设置反伪造令牌生成选项,排除用户名或类似内容,那将会很棒。