web-dev-qa-db-fra.com

MVC5 (VS2012) Identity CreateIdentityAsync - La valeur ne peut pas être nulle

J'essaie de configurer OAuth pour un site MVC5 (dans VS2012).

J'utilise Fluent NHibernate. J'ai configuré mon propre Userstore et je passe un objet de référentiel pour accéder à l'objet de session NHibernate. Je passe mon magasin au fournisseur par défaut aspnet usermanager. Cela a finalement fonctionné pour l'enregistrement local et la connexion. Je n'essaie pas de configurer la connexion/l'enregistrement avec Facebook.

Il obtient un compte réussi. Ajoute un utilisateur dans la table des utilisateurs, ajoute un enregistrement dans la table des connexions, puis explose. Je n'ai pas implémenté de revendications dans le magasin d'utilisateurs, ni mis de collection de revendications dans l'objet utilisateur. (Je ne sais pas si cela est réellement nécessaire, je supprimais tout ce qui pouvait mal tourner pour trouver la source du problème).

La ligne qui explose est, (dans le contrôleur de compte):

var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);            

au sein de cette méthode:

private async Task SignInAsync(IdentityUser user, bool isPersistent)

c'est la fin de la trace de la pile

[ArgumentNullException: Value cannot be null.
Parameter name: value]
   System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue) +14108789
   System.Security.Claims.Claim..ctor(String type, String value, String valueType) +62
   Microsoft.AspNet.Identity.<CreateAsync>d__0.MoveNext() +481
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +49
   Web.Controllers.<SignInAsync>d__42.MoveNext() in d:\Google Drive\Development\GoalManagement\Web\Controllers\AccountController.cs:375
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
   Web.Controllers.<ExternalLoginConfirmation>d__35.MoveNext() in d:\Google Drive\Development\GoalManagement\Web\Controllers\AccountController.cs:311
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84

public class IdentityUser : IUser
{
    public IdentityUser()
    {
        Logins = new List<IdentityUserLogin>();
    }

    public string Id { get; set; }
    public string UserName { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
    public IList<IdentityUserLogin> Logins { get; set; }

}

public class IdentityUserLogin
{
    public string LoginProvider { get; set; }
    public string ProviderKey { get; set; }
}

Je peux inclure mon code de magasin utilisateur si je le souhaite: je ne l'ai pas mis car il s'agit d'un fichier volumineux et pourrait nuire au problème.

Je ne sais pas pourquoi il essaie même de créer l'objet revendiqué et pourquoi il explose. Comme je n'ai que VS2012, j'ai tout corrigé à partir d'exemples en ligne principalement.


Comme suggéré par @Shoe, j'ai hérité de UserManager:

public class NHibernateAspnetUserManager<TUser> : UserManager<TUser> where TUser : IdentityUser
{
    public NHibernateAspnetUserManager(IUserStore<TUser> store) : base(store)
    {
    }        

    public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
    {
        ClaimsIdentity identity = new ClaimsIdentity();
        return Task.FromResult(identity);
    }
}

Désormais, il ne génère plus d'erreur, mais ne m'authentifie plus combien de fois j'utilise l'enregistrement/la connexion Facebook.


Résumer. Avec les informations de @ Shoe, j'ai essayé à la fois _ UserManager.CreateIdentityAsync avec:

public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
    {
        var identity = new ClaimsIdentity();
        identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
        return Task.FromResult(identity);
    }

et également essayer d'implémenter IUserClaimStore avec la valeur par défaut renvoyée (liste vide).

Le premier ne passera pas par une erreur mais ne finira pas authentifié. Ce dernier passera toujours par l'erreur de revendication étrange "System.Security.Claims.Claim..ctor"

MODIFIER

J'ai découvert pourquoi l'erreur ctor se produisait. L'objet utilisateur revenait sans l'ID, donc le UserManager par défaut se dérangeait. A corrigé cela et a utilisé le UserManager par défaut qui ne génère plus d'erreur, mais ne connecte toujours pas l'utilisateur. L'objet d'identité qu'il retourne semble bon d'après ce que je peux dire.

32
Jon

J'ai eu la même erreur dans le passé, mais uniquement lorsque j'ai créé un utilisateur avec Entity Framework Migration Tool. Lors de la création d'un utilisateur et de la signature sur le site Web, je n'avais pas d'erreur.

Mon erreur était que je ne fournissais pas SecurityStamp avec la migration.

SecurityStamp = Guid.NewGuid().ToString()

Cet ensemble de propriétés, tout fonctionnait.

79
Patrick Desjardins

J'avais un problème similaire. La solution consistait à définir la propriété SecurityStamp de l'entité utilisateur.

Contexte: Le client souhaite avoir des comptes d'administrateur/superutilisateur avec des mots de passe dans la base de données et un groupe d'utilisateurs supplémentaires - qui pourront se connecter sans mot de passe - dans un fichier XML ...

J'ai donc hérité du Entity Framework UserStore, outrepassé FindByIdAsync et FindByNameAsync, recherché le fichier XML pour l'utilisateur et renvoyé une nouvelle entité utilisateur. (si aucun utilisateur n'a été trouvé par l'implémentation par défaut)

J'ai eu la même exception que Jon lors de la création d'une ClaimsIdentity.

Après quelques recherches, j'ai constaté que mes entités utilisateur nouvellement créées n'avaient pas de tampon de sécurité. Et le UserManager par défaut asp.net attend un SecurityStamp et souhaite le définir comme une revendication dans la ClaimsIdentity.

Après avoir défini une valeur pour cette propriété - j'ai utilisé une chaîne qui contient un préfixe et le nom d'utilisateur - tout fonctionne bien pour moi.

13
Andreas Pircher

J'ai fait la même chose que @ user3347549.

Il m'a fallu un certain temps pour comprendre d'où venait l'erreur, bravo à dotPeek pour ça!

J'utilise ma propre implémentation de UserManager et UserStore, parce que je voulais des types Guid (identificateur unique dans MSSQL) comme clés, et non une chaîne (même si ce ne sont que des espaces réservés pour Guids)

Merci à ce lien et spécifiquement cette réponse, que j'ai incluse pour référence au cas où le lien disparaît, par HaoK (@Hao Kung ici sur SO):

Vous devez semer le tampon de sécurité avec quelque chose d'aléatoire, comme un nouveau travail de guidage.

J'ai implémenté ma propre ClaimsIdentityFactory (qui ressemble exactement à ce que je rassemble dans dotPeek) et j'ai juste modifié une ligne dans la méthode CreateAsync

public class ClaimsIdentityFactory<TUser, TKey> : IClaimsIdentityFactory<TUser, TKey>
    where TUser : class, IUser<TKey>
    where TKey : IEquatable<TKey>
{
    /// <summary>
    /// Claim type used for role claims
    /// </summary>
    public string RoleClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user name
    /// </summary>
    public string UserNameClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user id
    /// </summary>
    public string UserIdClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user security stamp
    /// </summary>
    public string SecurityStampClaimType { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public ClaimsIdentityFactory()
    {
        RoleClaimType = "http://schemas.Microsoft.com/ws/2008/06/identity/claims/role";
        UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
        UserNameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
        SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
    }

    /// <summary>
    /// Create a ClaimsIdentity from a user
    /// </summary>
    /// <param name="manager">
    /// </param>
    /// <param name="user">
    /// </param>
    /// <param name="authenticationType">
    /// </param>
    /// <returns>
    /// </returns>
    public virtual async Task<ClaimsIdentity> CreateAsync(UserManager<TUser, TKey> manager, TUser user, string authenticationType)
    {
        if (manager == null)
            throw new ArgumentNullException("manager");
        if (user == null)
            throw new ArgumentNullException("user");

        var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
        id.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), "http://www.w3.org/2001/XMLSchema#string"));
        id.AddClaim(new Claim(UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
        id.AddClaim(new Claim("http://schemas.Microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
        if (manager.SupportsUserSecurityStamp)
        {
            ClaimsIdentity claimsIdentity1 = id;
            string securityStampClaimType = SecurityStampClaimType;
            ClaimsIdentity claimsIdentity2 = claimsIdentity1;
            string str = await manager.GetSecurityStampAsync(user.Id).ConfigureAwait(false);
            Claim claim = new Claim(securityStampClaimType, str ?? Guid.NewGuid().ToString());
            claimsIdentity2.AddClaim(claim);
        }
        if (manager.SupportsUserRole)
        {
            IList<string> roles = await manager.GetRolesAsync(user.Id).ConfigureAwait(false);
            foreach (string str in roles)
                id.AddClaim(new Claim(RoleClaimType, str, "http://www.w3.org/2001/XMLSchema#string"));
        }
        if (manager.SupportsUserClaim)
            id.AddClaims(await manager.GetClaimsAsync(user.Id).ConfigureAwait(false));
        return id;
    }

    /// <summary>
    /// Convert the key to a string, by default just calls .ToString()
    /// </summary>
    /// <param name="key">
    /// </param>
    /// <returns>
    /// </returns>
    protected virtual string ConvertIdToString(TKey key)
    {
        if ((object)key == null)
            throw new ArgumentNullException("key");
        else
            return key.ToString();
    }
}

La ligne que j'ai modifiée venait de

Claim claim = new Claim(securityStampClaimType, str);

à

Claim claim = new Claim(securityStampClaimType, str ?? Guid.NewGuid().ToString());

Je n'ai pas encore compris ce que cela signifie, mais au moins cela fonctionne pour l'instant et je peux continuer à tester mon application. Je suppose que cette erreur apparaît parce que je n'ai pas complètement implémenté une partie de la pile d'identité. Pour utiliser cette nouvelle usine, tapez simplement ceci dans le constructeur UserManager:

ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser, Guid>();
2
Chris Klingsater

Dans mon cas, c'était quelque chose de totalement différent. C'était une question de commande de code de démarrage Owin

Mon code buggy:

public void ConfigureAuth(IAppBuilder app)
{

   //...

   app.CreatePerOwinContext(ApplicationDbContext.Create);
   app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
   app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
   app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

   //...

}

Il s'avère que AppSignInManager essayait d'initier AppUserManager qui est toujours null car il n'a pas encore été ajouté à Owin.

En les échangeant simplement ensemble, tout fonctionnait comme un charme

public void ConfigureAuth(IAppBuilder app)
{

   //...

   app.CreatePerOwinContext(ApplicationDbContext.Create);
   app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
   app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
   app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

   //...

}
0
Korayem

J'ai dû implémenter ClaimsIdentityFactory et définir la propriété UserManager.ClaimsIdentityFactory, c'est-à-dire dans la classe AccountController.

0
user3347549