Tworzę aplikację jednostronicową i mam problem z tokenami chroniącymi przed fałszerstwem.
Wiem, dlaczego tak się dzieje, ale nie wiem, jak go naprawić.
Pojawia się błąd, gdy dzieje się co następuje:
- Niezalogowany użytkownik ładuje okno dialogowe (z wygenerowanym tokenem zabezpieczającym przed fałszerstwem)
- Użytkownik zamyka okno dialogowe
- Użytkownik loguje się
- Użytkownik otwiera to samo okno dialogowe
- Użytkownik przesyła formularz w oknie dialogowym
Token zabezpieczający przed fałszerstwem jest przeznaczony dla użytkownika „”, ale aktualnym użytkownikiem jest „nazwa użytkownika”
Powodem tego jest to, że moja aplikacja jest w 100% jednostronicowa, a kiedy użytkownik loguje się pomyślnie za pośrednictwem posta w AJAX /Account/JsonLogin
, po prostu przełączam bieżące widoki z „widokami uwierzytelnionymi” zwróconymi z serwera, ale nie ładuję ponownie strona.
Wiem, że to jest powód, ponieważ jeśli po prostu przeładuję stronę między krokami 3 i 4, nie ma błędu.
Wygląda więc na to, że @Html.AntiForgeryToken()
w załadowanej formie nadal zwraca token dla starego użytkownika, dopóki strona nie zostanie ponownie załadowana.
Jak mogę zmienić, @Html.AntiForgeryToken()
aby zwrócić token dla nowego, uwierzytelnionego użytkownika?
Wstrzykuję nowy GenericalPrincipal
z niestandardowym IIdentity
na co Application_AuthenticateRequest
tak, zanim @Html.AntiForgeryToken()
zostanie wywołany HttpContext.Current.User.Identity
, w rzeczywistości moja niestandardowa tożsamość z IsAuthenticated
właściwością ustawioną na true, a mimo to @Html.AntiForgeryToken
nadal wydaje się renderować token dla starego użytkownika, chyba że wykonam ponowne załadowanie strony.
źródło
Odpowiedzi:
Dzieje się tak, ponieważ token chroniący przed fałszerstwem osadza nazwę użytkownika jako część zaszyfrowanego tokenu w celu lepszej weryfikacji. Przy pierwszym wywołaniu
@Html.AntiForgeryToken()
użytkownik nie jest zalogowany, więc token będzie miał pusty ciąg dla nazwy użytkownika, po zalogowaniu się użytkownika, jeśli nie zmienisz tokena przeciw fałszerstwu, nie przejdzie weryfikacji, ponieważ token początkowy był dla anonimowy użytkownik, a teraz mamy uwierzytelnionego użytkownika o znanej nazwie użytkownika.Masz kilka możliwości rozwiązania tego problemu:
Tylko tym razem pozwól swojemu SPA wykonać pełny POST, a po ponownym załadowaniu strony będzie miał token zapobiegający fałszerstwom z osadzoną zaktualizowaną nazwą użytkownika.
Miej częściowy widok z samym
@Html.AntiForgeryToken()
i zaraz po zalogowaniu, wykonaj kolejne żądanie AJAX i zastąp istniejący token zabezpieczający przed fałszerstwem odpowiedzią na żądanie.Po prostu wyłącz sprawdzanie tożsamości, które przeprowadza weryfikacja przed fałszerstwem. Dodaj następującą do Application_Start metody:
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true
.źródło
Aby naprawić błąd, musisz umieścić
OutputCache
adnotację danych na stronie pobieraniaActionResult
logowania jako:[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")] public ActionResult Login(string returnUrl)
źródło
Z moją aplikacją zdarza się to wiele razy, więc zdecydowałem się poszukać w Google!
Znalazłem proste wyjaśnienie tego błędu! Użytkownik dwukrotnie klika przycisk logowania! Możesz zobaczyć, jak inny użytkownik mówi o tym pod poniższym linkiem:
MVC 4 podał, że token zabezpieczający przed fałszerstwem był przeznaczony dla użytkownika „”, ale bieżący użytkownik to „użytkownik”
Mam nadzieję, że to pomoże! =)
źródło
Komunikat pojawia się po zalogowaniu, gdy jesteś już uwierzytelniony.
Ten pomocnik robi dokładnie to samo, co
[ValidateAntiForgeryToken]
atrybut.Usuń
[ValidateAntiForgeryToken]
atrybut z kontrolera i umieść tego pomocnika w metodzie działania.Jeśli więc użytkownik jest już uwierzytelniony, przekieruj na stronę główną, a jeśli nie, kontynuuj weryfikację ważnego tokena chroniącego przed fałszerstwem po tej weryfikacji.
if (User.Identity.IsAuthenticated) { return RedirectToAction("Index", "Home"); } System.Web.Helpers.AntiForgery.Validate();
źródło
Miałem ten sam problem i ten brudny hack go naprawił, przynajmniej do czasu, gdy mogę to naprawić w czystszy sposób.
public ActionResult Login(string returnUrl) { if (AuthenticationManager.User.Identity.IsAuthenticated) { AuthenticationManager.SignOut(); return RedirectToAction("Login"); }
...
źródło
Mam ten sam wyjątek, który występuje przez większość czasu na serwerze produkcyjnym.
Dlaczego tak się dzieje?
Dzieje się tak, gdy użytkownik loguje się z ważnymi poświadczeniami i po zalogowaniu się i przekierowaniu na inną stronę, a po naciśnięciu przycisku Wstecz wyświetli stronę logowania i ponownie wprowadzi prawidłowe poświadczenia, kiedy wystąpi ten wyjątek.
Jak rozwiązać?
Po prostu dodaj tę linię i działaj idealnie, bez błędu.
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
źródło
Miałem dość specyficzny, ale podobny problem w procesie rejestracji. Po kliknięciu przez użytkownika odsyłacza e-mail wysłanego do niego zostanie zalogowany i wysłany bezpośrednio do ekranu szczegółów konta, aby podać więcej informacji. Mój kod to:
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
Okazało się, że widok zwrotu („AccountDetails”) dawał mi wyjątek dotyczący tokena, zgaduję, ponieważ funkcja ConfirmEmail została ozdobiona wartością AllowAnonymous, ale funkcja AccountDetails miała ValidateAntiForgeryToken.
Zmiana opcji Return to Return RedirectToAction („AccountDetails”) rozwiązała problem za mnie.
źródło
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")] public ActionResult Login(string returnUrl)
Możesz to sprawdzić, umieszczając punkt przerwania w pierwszym wierszu akcji Zaloguj (Pobierz). Przed dodaniem dyrektywy OutputCache punkt przerwania zostałby osiągnięty przy pierwszym załadowaniu, ale po kliknięciu przycisku wstecz przeglądarki nie. Po dodaniu dyrektywy powinieneś zakończyć za każdym razem trafienie w punkt przerwania, więc AntiForgeryToken będzie tym poprawnym, a nie pustym.
źródło
Miałem ten sam problem z jednostronicową aplikacją ASP.NET MVC Core. Rozwiązałem to, ustawiając
HttpContext.User
we wszystkich akcjach kontrolera, które zmieniają bieżące oświadczenia o tożsamości (ponieważ MVC robi to tylko dla kolejnych żądań, jak omówiono tutaj ). Użyłem filtru wyników zamiast oprogramowania pośredniego, aby dołączyć pliki cookie zapobiegające fałszerstwom do moich odpowiedzi, co upewniło się, że zostały wygenerowane dopiero po powrocie akcji MVC.Kontroler (NB. Zarządzam użytkownikami za pomocą 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" }); } }
Filtr wyników umożliwiający dołączenie plików cookie zapobiegających fałszerstwom:
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) { } }
Ekstrakt 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) { //... } }
źródło
Ma problem z walidacją anty-fałszerstwa w sklepie internetowym: użytkownicy otwierają wiele zakładek (z towarem) i po zalogowaniu się na jednej próbują zalogować się na innym i dostaje taki AntiForgeryException. Więc AntiForgeryConfig.SuppressIdentityHeuristicChecks = true mi nie pomogło, więc użyłem takiego brzydkiego hackfixa, może komuś się przyda:
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()); } }
Pomyśl, że będzie wspaniale, jeśli można ustawić opcje generowania tokenów przed fałszerstwem, aby wykluczyć nazwę użytkownika lub coś w tym rodzaju.
źródło