Problem z tokenem chroniącym przed fałszerstwem (MVC 5)


122

Mam problem z tokenem chroniącym przed fałszerstwem :( Utworzyłem własną klasę użytkownika, która działała dobrze, ale teraz pojawia się błąd za każdym razem, gdy przechodzę do strony / Konto / Rejestr . Błąd:

Zgłoszenie typu „ http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier ” lub „ http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider ” zostało brak na podanym ClaimsIdentity. Aby włączyć obsługę tokenu chroniącego przed fałszerstwem z uwierzytelnianiem opartym na oświadczeniach, sprawdź, czy skonfigurowany dostawca oświadczeń udostępnia oba te oświadczenia w generowanych przez siebie wystąpieniach ClaimsIdentity. Jeśli zamiast tego skonfigurowany dostawca oświadczeń używa innego typu oświadczenia jako unikatowego identyfikatora, można go skonfigurować, ustawiając właściwość statyczną AntiForgeryConfig.UniqueClaimTypeIdentifier.

Znalazłem ten artykuł:

http://stack247.wordpress.com/2013/02/22/antiforgerytoken-a-claim-of-type-nameidentifier-or-identityprovider-was-not-present-on-provided-claimsidentity/

więc zmieniłem metodę Application_Start na to:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;
}

ale kiedy to robię, pojawia się ten błąd:

Roszczenie typu „ http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress ” nie było obecne w podanym ClaimsIdentity.

Czy ktoś wcześniej się z tym spotkał? Jeśli tak, czy wiesz, jak go rozwiązać?

Pozdrawiam z góry,
r3plica

Zaktualizuj 1

Oto moja niestandardowa klasa użytkownika:

public class Profile : User, IProfile
{
    public Profile()
        : base()
    {
        this.LastLoginDate = DateTime.UtcNow;
        this.DateCreated = DateTime.UtcNow;
    }

    public Profile(string userName)
        : base(userName)
    {
        this.CreatedBy = this.Id;

        this.LastLoginDate = DateTime.UtcNow;
        this.DateCreated = DateTime.UtcNow;

        this.IsApproved = true;
    }

    [NotMapped]
    public HttpPostedFileBase File { get; set; }

    [Required]
    public string CompanyId { get; set; }

    [Required]
    public string CreatedBy { get; set; }
    public string ModifiedBy { get; set; }

    public DateTime DateCreated { get; set; }
    public DateTime? DateModified { get; set; }
    public DateTime LastLoginDate { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredTitle")]
    public string Title { get; set; }
    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredFirstName")]
    public string Forename { get; set; }
    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredLastName")]
    public string Surname { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredEmail")]
    public string Email { get; set; }
    public string JobTitle { get; set; }
    public string Telephone { get; set; }
    public string Mobile { get; set; }
    public string Photo { get; set; }
    public string LinkedIn { get; set; }
    public string Twitter { get; set; }
    public string Facebook { get; set; }
    public string Google { get; set; }
    public string Bio { get; set; }

    public string CompanyName { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredCredentialId")]
    public string CredentialId { get; set; }
    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredSecurityCode")]
    public bool IsLockedOut { get; set; }
    public bool IsApproved { get; set; }

    [Display(Name = "Can only edit own assets")]
    public bool CanEditOwn { get; set; }
    [Display(Name = "Can edit assets")]
    public bool CanEdit { get; set; }
    [Display(Name = "Can download assets")]
    public bool CanDownload { get; set; }
    [Display(Name = "Require approval to upload assets")]
    public bool RequiresApproval { get; set; }
    [Display(Name = "Can approve assets")]
    public bool CanApprove { get; set; }
    [Display(Name = "Can synchronise assets")]
    public bool CanSync { get; set; }

    public bool AgreedTerms { get; set; }
    public bool Deleted { get; set; }
}

public class ProfileContext : IdentityStoreContext
{
    public ProfileContext(DbContext db)
        : base(db)
    {
        this.Users = new UserStore<Profile>(this.DbContext);
    }
}

public class ProfileDbContext : IdentityDbContext<Profile, UserClaim, UserSecret, UserLogin, Role, UserRole>
{
}

Mój profil jest prosty dla moich repozytoriów, wygląda tak:

public interface IProfile
{
    string Id { get; set; }
    string CompanyId { get; set; }

    string UserName { get; set; }
    string Email { get; set; }

    string CredentialId { get; set; }
}

a klasa User to Microsoft.AspNet.Identity.EntityFramework.User . Mój AccountController wygląda następująco:

[Authorize]
public class AccountController : Controller
{
    public IdentityStoreManager IdentityStore { get; private set; }
    public IdentityAuthenticationManager AuthenticationManager { get; private set; }

    public AccountController() 
    {
        this.IdentityStore = new IdentityStoreManager(new ProfileContext(new ProfileDbContext()));
        this.AuthenticationManager = new IdentityAuthenticationManager(this.IdentityStore);
    }

    //
    // GET: /Account/Register
    [AllowAnonymous]
    public ActionResult Register()
    {
        return View();
    }

    //
    // POST: /Account/Register
    [HttpPost]
    [AllowAnonymous]
    public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            try
            {
                // Create a profile, password, and link the local login before signing in the user
                var companyId = Guid.NewGuid().ToString();
                var user = new Profile(model.UserName)
                {
                    CompanyId = companyId,
                    Title = model.Title,
                    Forename = model.Forename,
                    Surname = model.Surname,
                    Email = model.Email,
                    CompanyName = model.CompanyName,
                    CredentialId = model.CredentialId
                };

                if (await IdentityStore.CreateLocalUser(user, model.Password))
                {
                    //Create our company
                    var company = new Skipstone.Web.Models.Company()
                    {
                        Id = companyId,
                        CreatedBy = user.Id,
                        ModifiedBy = user.Id,
                        Name = model.CompanyName
                    };

                    using (var service = new CompanyService())
                    {
                        service.Save(company);
                    }

                    await AuthenticationManager.SignIn(HttpContext, user.Id, isPersistent: false);
                    return RedirectToAction("Setup", new { id = companyId });
                }
                else
                {
                    ModelState.AddModelError("", "Failed to register user name: " + model.UserName);
                }
            }
            catch (IdentityException e)
            {
                ModelState.AddModelError("", e.Message);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    //
    // POST: /Account/Setup
    public ActionResult Setup(string id)
    {
        var userId = User.Identity.GetUserId();
        using (var service = new CompanyService())
        {
            var company = service.Get(id);
            var profile = new Profile()
            {
                Id = userId,
                CompanyId = id
            };

            service.Setup(profile);

            return View(company);
        }
    }
}

Kiedyś był ozdobiony atrybutem [ValidateAntiForgeryToken] , ale w tym miejscu przestał działać.

Mam nadzieję, że to wystarczy :)


Czy możesz pokazać nam niestandardową klasę użytkownika i sposób jej wykorzystania?
LostInComputer

Dodałem niestandardową klasę użytkownika oraz sposób, w jaki jej używam.
r3plica

Używasz wersji beta. Proponuję uaktualnić do wersji wydania, a następnie sprawdzić, czy problem nadal występuje.
LostInComputer

Odpowiedzi:


230

Spróbuj ustawić (w global.cs):

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;

33
Myślę, że ważne jest, aby zanotować, dlaczego to działa: To mówi AntiForgeryklasie, aby użyła NameIdentifier(który jest ciągiem identyfikatora użytkownika znalezionym przez GetUserId). Dziękuję Mike'owi Goodwinowi, który pomógł mi się tego nauczyć!
Matt DeKrey

Próbowałem "AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;" i otrzymałem ten błąd „Sekwencja zawiera więcej niż jeden pasujący element”, w moim przypadku jest kilka roszczeń (nazwa, rola i adres e-mail). Jak mogę to rozwiązać?
Dhanuka777

9
Ustawiłem to w Global.asax.cs
Mike Taverne

6
Jest to również rozwiązanie, jeśli używasz OpenId (tj. Azure ActiveDirectory) jako uwierzytelniania.
guysherman

6
Pełne przestrzenie nazw ... Musiałem trochę poszperać, aby dowiedzieć się, gdzie odbywa się ClaimTypes. System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier = System.Security.Claims.ClaimTypes.NameIdentifier;
Mark Rowe

65

Czy wiesz, jakie roszczenia otrzymujesz w swojej ClaimsIdentity? Jeśli nie:

  1. Usuń [ValidateAntiForgeryToken]atrybut
  2. Umieść punkt przerwania gdzieś w kontrolerze i złam go
  3. Następnie spójrz na aktualne ClaimsIdentityi przeanalizuj roszczenia
  4. Znajdź taki, który Twoim zdaniem będzie jednoznacznie identyfikował Twojego użytkownika
  5. Ustaw na AntiForgeryConfig.UniqueClaimTypeIdentifierten typ oświadczenia
  6. Odłóż [ValidateAntiForgeryToken]atrybut

3
To więcej niż udzielenie bezpośredniej odpowiedzi na temat karmienia łyżką, ta przedstawia tło i umożliwia odkrycie samego siebie. :)
Wielkie

2
6. Przywróć [ValidateAntiForgeryToken]atrybut
Scott Fraley

1
to naprawdę mi pomogło. Okazało się, że dostałem roszczenie do innej aplikacji działającej na moim hoście lokalnym, w mojej aplikacji, w której nie zastosowano żadnych roszczeń (dlatego twierdzenia brzmiały dla mnie dziwnie). Kiedy więc wylogowałem się z innej aplikacji, roszczenia zniknęły, podobnie jak błąd. W środowisku testowym na żywo strony te są bardziej rozdzielone. Myślę więc, że powyższe rozwiązanie jest mi potrzebne, ale tylko do lokalnego rozwoju.
Michel

26

Po prostu umieść to w global.asax.cs

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;

Dzięki. Nie rozumiem, dlaczego musiałem dokonać tej zmiany, naprawiłem kilka różnych problemów, które miałem z moim kodem zeszłej nocy i wszystko działało dobrze. Nie zmieniając niczego, przetestowałem to na innej maszynie i wszystko działało jeszcze kilka minut temu.
Artorias2718

14

Spróbuj otworzyć link w oknie incognito lub wyczyść plik cookie z tej domeny (tj. Localhost).


Dlaczego to działa i jaka jest przyczyna problemu?
mok

Działa to, ponieważ gdy masz sesyjny plik cookie z nieprawidłowym identyfikatorem nazwy, serwer próbuje użyć nieprawidłowego identyfikatora bez przekierowywania użytkownika na stronę logowania i uzyskiwania właściwego identyfikatora nazwy.
rawel

3

Edycja: Mając w tej chwili lepsze zrozumienie tego problemu, możesz zignorować moją odpowiedź poniżej.

Ustawienie AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;w Application_Start () z Global.asax.cs naprawiło to za mnie. Mimo że mam http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifierustawione roszczenie , pojawia się ten sam błąd, co w pierwotnym pytaniu. Ale wskazanie tego jak powyżej jakoś działa.



Począwszy od MVC4, token zapobiegający fałszowaniu nie jest używany User.Identity.Namejako unikalny identyfikator. Zamiast tego szuka dwóch oświadczeń podanych w komunikacie o błędzie.

Aktualizacja UWAGA: To nie powinno być potrzebne.Możesz dodać brakujące roszczenia do swojego ClaimsIdentity, gdy użytkownik jest zalogowany, na przykład:

string userId = TODO;
var identity = System.Web.HttpContext.Current.User.Identity as ClaimsIdentity;
identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", userId));
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", userId));

Zwróć uwagę, że jedno z roszczeń może już istnieć wcześniej, a jeśli dodasz oba, pojawi się błąd z powielonymi roszczeniami. Jeśli tak, po prostu dodaj brakujący.


1
Rozumiem, dlaczego używasz userId jako „/ nameidentifier”, ale dlaczego wstawiasz userId jako „/ identityprovider”?
AaronLS

2

W Global.asax.cs,

1. Dodaj te przestrzenie nazw

using System.Web.Helpers;
using System.Security.Claims;

2. Dodaj tę linię w metodzie Application_Start:

 protected void Application_Start()
 {
       .......
       AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
 } 

W jaki sposób dodaje to więcej wartości niż te, na które udzielono odpowiedzi powyżej
NitinSingh

Dzięki za dodanie zastosowań. @NitinSingh Myślę, że to dodaje więcej wartości, ponieważ nie wiedziałem, której z trzech potencjalnych przestrzeni nazw w moim projekcie użyć.
Keisha W

Za każdym razem, gdy dodasz nową funkcjonalność, poprosi o prawidłowe odniesienia. Po kompilacji należy usunąć nieużywane za pomocą menu Refactor po kliknięciu prawym przyciskiem myszy
NitinSingh

0
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;

działa w moim przypadku używam uwierzytelniania ADFS.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.