Problema de token anti-falsificación (MVC 5)

122

Tengo un problema con el token anti-falsificación :( He creado mi propia clase de usuario que funcionó bien, pero ahora recibo un error cada vez que voy a la página / Cuenta / Registro . El error es:

Un reclamo del tipo ' http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier ' o ' http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider ' fue no presente en el ClaimsIdentity proporcionado. Para habilitar el soporte de token anti-falsificación con autenticación basada en reclamos, verifique que el proveedor de reclamos configurado proporcione ambos reclamos en las instancias de ClaimsIdentity que genera. Si, en cambio, el proveedor de notificaciones configurado utiliza un tipo de notificación diferente como identificador único, se puede configurar estableciendo la propiedad estática AntiForgeryConfig.UniqueClaimTypeIdentifier.

Encontré este artículo:

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

así que cambié mi método Application_Start a esto:

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

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

    AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;
}

pero cuando hago eso, aparece este error:

Un reclamo del tipo ' http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress ' no estaba presente en el ClaimsIdentity proporcionado.

¿Alguien se ha encontrado con esto antes? Si es así, ¿sabes cómo solucionarlo?

Saludos de antemano,
r3plica

Actualización 1

Aquí está mi clase de usuario 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>
{
}

Mi perfil es simple para mis repositorios, se ve así:

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

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

    string CredentialId { get; set; }
}

y la clase de usuario es la clase Microsoft.AspNet.Identity.EntityFramework.User . Mi AccountController se ve así:

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

Solía ​​estar decorado con el atributo [ValidateAntiForgeryToken] , pero ahí es donde dejó de funcionar.

Espero que sea suficiente código :)

r3plica
fuente
¿Puede mostrarnos la clase de usuario personalizada y cómo la utilizó?
LostInComputer
He agregado la clase de usuario personalizada, además de cómo la estoy usando.
r3plica
Estás usando la versión beta. Le sugiero que actualice a la versión de lanzamiento y luego vea si el problema persiste.
LostInComputer

Respuestas:

230

Intente configurar (en global.cs):

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
Alex Filipovici
fuente
33
Creo que es importante tener en cuenta por qué funciona esto: esto le dice a la AntiForgeryclase que use NameIdentifier(que es la cadena de identificación de usuario encontrada por GetUserId). ¡Gracias a la respuesta de Mike Goodwin por ayudarme a aprender esto!
Matt DeKrey
Intenté "AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;" y obtuve este error "La secuencia contiene más de un elemento coincidente", en mi caso hay varios reclamos (nombre, rol y dirección de correo electrónico). ¿Cómo puedo solucionar esto?
Dhanuka777
9
Configuré esto en Global.asax.cs
Mike Taverne
6
Esta es también la solución si está utilizando OpenId (es decir, Azure ActiveDirectory) como su autenticación.
guysherman
6
Espacios de nombres completos ... Tuve que investigar un poco para averiguar dónde se llevó a cabo ClaimTypes. System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier = System.Security.Claims.ClaimTypes.NameIdentifier;
Mark Rowe
65

¿Sabe qué reclamos obtiene en su ClaimsIdentity? Si no:

  1. Eliminar el [ValidateAntiForgeryToken]atributo
  2. Coloque un punto de interrupción en algún lugar de su controlador y rompa en él
  3. Entonces mira la corriente ClaimsIdentity y examine las afirmaciones.
  4. Encuentre uno que crea que identificará de manera única a su usuario
  5. Establecer el AntiForgeryConfig.UniqueClaimTypeIdentifieren ese tipo de reclamo
  6. Devolver el [ValidateAntiForgeryToken]atributo
Mike Goodwin
fuente
3
Más que proporcionar la respuesta directa de alimentación con cuchara, esta cuenta los antecedentes y permite el autodescubrimiento. :) Muchas gracias
NitinSingh
2
6. Vuelva a colocar el [ValidateAntiForgeryToken]atributo
Scott Fraley
1
esto realmente me ayudó. Resultó que obtuve un reclamo de otra aplicación que se ejecuta en mi host local, en mi aplicación donde no se utilizan reclamos (por lo que el asunto de los reclamos me sonó extraño). Entonces, cuando me desconecté de la otra aplicación, las reclamaciones desaparecieron y también el error. En el entorno de prueba en vivo, estos sitios están más separados. Entonces creo que necesito la solución mencionada, pero solo para el desarrollo local.
Michel
26

Simplemente ponga esto en global.asax.cs

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
mzk
fuente
Gracias. Lo que no entiendo es por qué tuve que hacer este cambio, solucioné algunos problemas diferentes que tenía con mi código anoche y todo funcionó bien. Sin cambiar nada, probé esto en otra máquina y todo funcionó hasta hace unos minutos.
Artorias2718
14

Intente abrir el enlace en la ventana de incógnito o borrar la cookie de ese dominio (es decir, localhost).

Gurgen Sargsyan
fuente
¿Por qué funciona esto y cuál es la causa del problema?
Mok
Esto funciona porque cuando tiene una cookie de sesión con un identificador de nombre no válido, el servidor intenta utilizar el identificador no válido sin redirigir al usuario a la página de inicio de sesión y obtener el identificador de nombre adecuado.
rawel
3

Editar: Teniendo una mayor comprensión de este problema en este momento, puede ignorar mi respuesta a continuación.

La configuración AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;en Application_Start () de Global.asax.cs lo arregló para mí. Aunque tengo el reclamo http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifierestablecido, obtengo el mismo error que en la pregunta original. Pero señalarlo como arriba funciona de alguna manera.



Comenzando con MVC4, el token anti-falsificación no se usa User.Identity.Namecomo identificador único. En su lugar, busca las dos afirmaciones dadas en el mensaje de error.

Actualización NOTA: Esto no debería ser necesario . Puede agregar las reclamaciones que faltan a su ClaimsIdentity cuando el usuario está iniciando sesión, así:

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

Tenga en cuenta que es posible que una de las reclamaciones ya esté allí desde antes, y obtendrá un error con reclamaciones duplicadas si agrega ambas. Si es así, simplemente agregue el que falta.

cederlof
fuente
1
Entiendo por qué está usando userId como "/ nameidentifier", pero ¿por qué está poniendo el userId como "/ identityprovider"?
AaronLS
2

En Global.asax.cs,

1.Añadir estos espacios de nombres

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

2.Agregue esta línea en el método Application_Start:

 protected void Application_Start()
 {
       .......
       AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
 } 
Kiran Chaudhari
fuente
¿Cómo agrega más valor que los respondidos anteriormente
NitinSingh
Gracias por agregar los usos. @NitinSingh Creo que eso agrega más valor porque no sabía cuál de los tres espacios de nombres potenciales en mi proyecto usar.
Keisha W
Siempre que agregue una nueva funcionalidad, le pedirá las referencias correctas. Una vez que se compila, debe eliminar los no utilizados a través del menú Refactor al hacer clic derecho
NitinSingh
0
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;

funciona para mi caso estoy usando la autenticación ADFS.

Ashu
fuente