Problema com token antifalsificação (MVC 5)

122

Estou tendo um problema com o token anti-falsificação :( Criei minha própria classe de usuário que funcionou bem, mas agora estou recebendo um erro sempre que vou à página / Conta / Registro . O erro é:

Uma reivindicação do tipo ' http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier ' ou ' http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider ' foi não está presente na ClaimIdentity fornecida. Para habilitar o suporte ao token anti-falsificação com autenticação baseada em declarações, verifique se o provedor de declarações configurado está fornecendo essas duas declarações nas instâncias de ClaimsIdentity que gera. Se o provedor de declarações configurado usar um tipo de declaração diferente como um identificador exclusivo, ele poderá ser configurado definindo a propriedade estática AntiForgeryConfig.UniqueClaimTypeIdentifier.

Encontrei este artigo:

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

então mudei meu método Application_Start para isso:

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

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

    AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;
}

mas quando faço isso, recebo este erro:

Uma reivindicação do tipo ' http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress ' não estava presente no ClaimIdentity fornecido.

Alguém já passou por isso antes? Se sim, você sabe como resolvê-lo?

Cheers antecipadamente,
r3plica

Atualização 1

Aqui está minha classe de usuário personalizada:

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>
{
}

O perfil é simples para meus repositórios, fica assim:

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

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

    string CredentialId { get; set; }
}

e a classe User é a classe Microsoft.AspNet.Identity.EntityFramework.User . My AccountController é assim:

[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);
        }
    }
}

Costumava ser decorado com o atributo [ValidateAntiForgeryToken] , mas é aí que ele parou de funcionar.

Espero que seja código suficiente :)

r3plica
fonte
Você pode nos mostrar a classe de usuário personalizada e como a usou?
LostInComputer
Eu adicionei a classe User personalizada, além de como a estou usando.
R3plica
Você está usando a versão beta. Eu sugiro que você atualize para a versão de lançamento e verifique se o problema ainda ocorre.
LostInComputer

Respostas:

230

Tente definir (em global.cs):

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
Alex Filipovici
fonte
33
Eu acho que é importante observar por que isso funciona: Isso diz à AntiForgeryclasse para usar o NameIdentifier(que é a string de identificação do usuário encontrada por GetUserId). Graças à resposta de Mike Goodwin para me ajudar a aprender isso!
Matt DeKrey
Eu tentei "AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;" e recebi esse erro "Sequência contém mais de um elemento correspondente", no meu caso, há várias reivindicações (nome, função e endereço de email). Como posso resolver isso?
Dhanuka777
9
Eu configurei isso no Global.asax.cs
Mike Taverne
6
Essa também é a solução se você estiver usando o OpenId (por exemplo, Azure ActiveDirectory) como sua autenticação.
guysherman
6
Namespaces completos. Eu tive que fazer algumas escavações para descobrir onde o ClaimTypes era realizado. System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier = System.Security.Claims.ClaimTypes.NameIdentifier;
22416 Mark-Rowe
65

Você sabe quais reivindicações você recebe no seu ClaimIdentity? Se não:

  1. Remova o [ValidateAntiForgeryToken]atributo
  2. Coloque um ponto de interrupção em algum lugar do seu controlador e interrompa
  3. Então olhe para a corrente ClaimsIdentitye examine as reivindicações
  4. Encontre um que você ache que identificará exclusivamente seu usuário
  5. Defina AntiForgeryConfig.UniqueClaimTypeIdentifierpara esse tipo de reivindicação
  6. Coloque de volta o [ValidateAntiForgeryToken]atributo
Mike Goodwin
fonte
3
Mais do que fornecer a resposta direta ao feed de colher, esta informa o plano de fundo e permite a autodescoberta. :) Muito obrigado
NitinSingh
2
6. Coloque de volta o [ValidateAntiForgeryToken]atributo
Scott Fraley
1
isso realmente me ajudou. Acontece que eu recebi uma reivindicação de outro aplicativo em execução no meu host local, no meu aplicativo em que não há declarações usadas (e é por isso que a coisa das declarações parecia estranha para mim). Portanto, quando eu saí do outro aplicativo, as reivindicações desapareceram e também ocorreu o erro. No ambiente de teste ao vivo, esses sites são mais separados. Então, acho que preciso da solução mencionada acima, mas apenas para o desenvolvimento local.
Michel
26

Basta colocar isso em global.asax.cs

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
mzk
fonte
Obrigado. O que eu não entendo é por que eu tive que fazer essa alteração. Corrigi alguns problemas diferentes que estava tendo com meu código na noite passada e tudo funcionou bem. Sem alterar nada, testei isso em outra máquina e tudo funcionou até alguns minutos atrás.
precisa saber é o seguinte
14

Tente abrir o link na janela anônima ou limpar o cookie desse domínio (por exemplo, localhost).

Gurgen Sargsyan
fonte
Por que isso funciona e qual é a causa do problema?
mok
Isso funciona porque quando você tem um cookie de sessão com identificador de nome inválido, o servidor tenta usar o identificador inválido sem redirecionar o usuário para a página de logon e obter o identificador de nome apropriado.
Rawel
3

Edit: Tendo uma maior compreensão deste problema neste momento, você pode desconsiderar minha resposta abaixo.

A configuração AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;no Application_Start () do Global.asax.cs foi corrigida para mim. Mesmo tendo a reivindicação http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifierdefinida, recebo o mesmo erro da pergunta original. Mas apontar como acima funciona de alguma forma.



A partir do MVC4, o token anti-falsificação não usa User.Identity.Namecomo identificador exclusivo. Em vez disso, procura as duas reivindicações fornecidas na mensagem de erro.

Atualização OBSERVAÇÃO: isso não deve ser necessário. Você pode adicionar as declarações ausentes ao seu ClaimsIdentity quando o usuário estiver conectado, da seguinte forma:

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));

Observe que uma das reivindicações já pode estar lá antes e você receberá um erro com reivindicações duplicadas se adicionar as duas. Nesse caso, basta adicionar o que está faltando.

cederlof
fonte
1
Entendo por que você está usando userId como o "/ identificador de nome", mas por que está inserindo o userId como o "/ identityprovider"?
AaronLS 22/02
2

No Global.asax.cs,

1. Adicione esses namespaces

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

2. Adicione esta linha no método Application_Start:

 protected void Application_Start()
 {
       .......
       AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
 } 
Kiran Chaudhari
fonte
Como ele agrega mais valor do que os respondidos acima?
NitinSingh
Obrigado por adicionar os usos. @NitinSingh Acho que agrega mais valor porque não sabia qual dos três namespaces em potencial no meu projeto usar.
Keisha W
Sempre que você adiciona uma nova funcionalidade, ela solicita referências corretas. Uma vez que ele compila, você deve remover os não utilizados através do menu Refactor do botão direito
NitinSingh
0
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;

funciona para o meu caso, estou usando autenticação ADFS.

Ashu
fonte