Токен защиты от подделки предназначен для пользователя "", но текущим пользователем является "имя пользователя"
Я создаю одностраничное приложение и испытываю проблему с токенами против подделки.
Я знаю, почему проблема возникает, я просто не знаю, как ее исправить.
Я получаю сообщение об ошибке, когда происходит следующее:
- Пользователь, не вошедший в систему, загружает диалоговое окно (с созданным токеном против подделки)
- Пользователь закрывает диалог
- Пользователь входит в систему
- Пользователь открывает тот же диалог
- Пользователь отправляет форму в диалоге
Токен защиты от подделки предназначен для пользователя "", но текущим пользователем является "имя пользователя"
Причина, по которой это происходит, заключается в том, что мое приложение на 100% состоит из одной страницы, и когда пользователь успешно входит в систему через сообщение ajax для /Account/JsonLogin
Я просто отключаю текущие представления с помощью "проверенных представлений", возвращаемых с сервера, но не перезагружаю страницу.
Я знаю, что это причина, потому что, если я просто перезагружаю страницу между шагами 3 и 4, ошибки не возникает.
Так что кажется, что @Html.AntiForgeryToken()
в загруженной форме по-прежнему возвращает токен для старого пользователя, пока страница не будет перезагружена.
Как я могу изменить @Html.AntiForgeryToken()
вернуть токен для нового аутентифицированного пользователя?
Я ввожу новый GenericalPrincipal
с обычаем IIdentity
на каждом Application_AuthenticateRequest
так к тому времени @Html.AntiForgeryToken()
вызывается HttpContext.Current.User.Identity
на самом деле моя обычная идентичность с IsAuthenticated
свойство установлено в true и все же @Html.AntiForgeryToken
кажется, все еще выдает маркер для старого пользователя, если я не делаю перезагрузку страницы.
10 ответов
Это происходит потому, что токен защиты от подделки встраивает имя пользователя как часть зашифрованного токена для лучшей проверки. Когда вы впервые звоните @Html.AntiForgeryToken()
пользователь не вошел в систему, поэтому токен будет иметь пустую строку для имени пользователя, после того как пользователь войдет в систему, если вы не замените токен защиты от подделки, он не пройдет проверку, поскольку исходный токен был для анонимного пользователя, и теперь мы иметь аутентифицированного пользователя с известным именем пользователя.
У вас есть несколько вариантов решения этой проблемы:
Только в этот раз пусть ваш SPA сделает полный POST, и когда страница перезагрузится, у него будет токен защиты от подделки со встроенным обновленным именем пользователя.
Иметь частичное представление только с
@Html.AntiForgeryToken()
и сразу после входа в систему сделайте еще один запрос AJAX и замените существующий токен противодействия подделке ответом на запрос.Просто отключите проверку подлинности, которую выполняет проверка подделки. Добавьте следующее в ваш метод Application_Start:
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true
,
Чтобы исправить ошибку, вам нужно разместить OutputCache
Аннотации данных на Get ActionResult
страницы входа в систему как:
[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")]
public ActionResult Login(string returnUrl)
Сообщение появляется при входе в систему, когда вы уже прошли аутентификацию.
Этот Помощник делает то же самое, что и [ValidateAntiForgeryToken]
приписывать.
System.Web.Helpers.AntiForgery.Validate()
Удалить [ValidateAntiForgeryToken]
приписать от контроллера и поместить этот помощник в метод действия.
Поэтому, когда пользователь уже прошел аутентификацию, перенаправьте его на домашнюю страницу или, если нет, продолжите проверку действительного токена для защиты от подделки после этой проверки.
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
System.Web.Helpers.AntiForgery.Validate();
Чтобы попытаться воспроизвести ошибку, сделайте следующее: Если вы находитесь на странице входа в систему и не прошли аутентификацию. Если вы дублируете вкладку, и вы входите со второй вкладкой. И если вы вернетесь к первой вкладке на странице входа и попытаетесь войти без перезагрузки страницы... у вас есть эта ошибка.
Это часто случается с моим приложением, поэтому я решил поискать его в Google!
Я нашел простое объяснение об этой ошибке! Пользователь дважды нажимает на кнопку для входа! Вы можете видеть, что другой пользователь говорит об этом по ссылке ниже:
Я надеюсь, что это помогает! знак равно
У меня была та же проблема, и этот грязный хак исправил ее, по крайней мере, до тех пор, пока я не смогу починить ее чище.
public ActionResult Login(string returnUrl)
{
if (AuthenticationManager.User.Identity.IsAuthenticated)
{
AuthenticationManager.SignOut();
return RedirectToAction("Login");
}
...
У меня такое же исключение происходит большую часть времени на производственном сервере.
Почему это происходит?
Это происходит, когда пользователь входит в систему с действительными учетными данными и после входа в систему и перенаправления на другую страницу, и после того, как они нажимают кнопку "Назад", отобразится страница входа в систему, и снова он ввел действительные учетные данные, что в этот раз произойдет исключение.
Как решить?
Просто добавьте эту строку и работайте без ошибок.
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
У меня была довольно специфическая, но похожая проблема в процессе регистрации. После того, как пользователь нажал на отправленную им ссылку электронной почты, он войдет в систему и отправит ее прямо на экран сведений об учетной записи, чтобы заполнить дополнительную информацию. Мой код был:
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 ("AccountDetails") давало мне исключение токена, я полагаю, потому что функция ConfirmEmail была украшена AllowAnonymous, но функция AccountDetails имела ValidateAntiForgeryToken.
Изменение Return to Return RedirectToAction("AccountDetails") решило проблему для меня.
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]
public ActionResult Login(string returnUrl)
Вы можете проверить это, поставив точку останова в первой строке действия Login (Get). Перед добавлением директивы OutputCache точка останова будет достигнута при первой загрузке, но после нажатия кнопки "Назад" в браузере не будет. После добавления директивы вы должны получать точку останова каждый раз, поэтому AntiForgeryToken будет основным, а не пустым.
У меня была такая же проблема с одностраничным приложением ASP.NET MVC Core. Я решил это, установив HttpContext.User
во всех действиях контроллера, которые изменяют текущие утверждения идентичности (так как MVC делает это только для последующих запросов, как обсуждено здесь). Я использовал фильтр результатов вместо промежуточного программного обеспечения для добавления файлов cookie для защиты от подделки к моим ответам, чтобы убедиться, что они были сгенерированы только после возврата действия MVC.
Контроллер (NB. Я управляю пользователями с помощью ASP.NET Core Identity):
[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 извлечь:
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)
{
//...
}
}
Возникла проблема с проверкой токенов анти-подделки в интернет-магазине: пользователи открывают много вкладок (с товарами) и, войдя в систему, пытаются войти в систему на другой и получают такое исключение AntiForgeryException. Итак, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true мне не помогло, поэтому я использовал такой уродливый хакфикс, может быть, он кому-нибудь пригодится:
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());
}
}
Думаю, будет здорово, если можно будет установить параметры генерации токенов подделки, чтобы исключить имя пользователя или что-то в этом роде.