自定义声明在身份重新验证时丢失

l.r*_*ndi 5 validation identity claims-based-identity asp.net-mvc-5

我正在使用Identity 2.x身份验证和授权模型实现Asp.NET MVC应用程序.

在LogIn过程中,我添加自定义声明(不会保留在数据库中!),从LogIn中传递的数据派生到Identity,我可以在以后正确访问它们,直到身份重新生成.

    [HttpPost]
    [AllowAnonymous]
    [ValidateHeaderAntiForgeryToken]
    [ActionName("LogIn")]
    public async Task<JsonResult> Login(LoginViewModel model, string returnUrl)
    {
        if (!ModelState.IsValid)
            return Json(GenericResponseViewModel.Failure(ModelState.GetErrors("Inavlid model", true)));


        using (var AppLayer = new ApplicationLayer(new ApplicationDbContext(), System.Web.HttpContext.Current))
        {
            GenericResponseViewModel LogInResult = AppLayer.Users.ValidateLogInCredential(ref model);
            if (!LogInResult.Status)
            {
                WebApiApplication.ApplicationLogger.ExtWarn((int)Event.ACC_LOGIN_FAILURE, string.Join(", ", LogInResult.Msg));
                return Json(LogInResult);
            }

            ApplicationUser User = (ApplicationUser)LogInResult.ObjResult;

            // In case of positive login I reset the failed login attempts count
            if (UserManager.SupportsUserLockout && UserManager.GetAccessFailedCount(User.Id) > 0)
                UserManager.ResetAccessFailedCount(User.Id);

            //// Add profile claims for LogIn
            User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "Culture", ClaimValue = model.Culture });
            User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CompanyId", ClaimValue = model.CompanyId });


            ClaimsIdentity Identity = await User.GenerateUserIdentityAsync(UserManager, DefaultAuthenticationTypes.ApplicationCookie);

            AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, Identity);

            WebApiApplication.ApplicationLogger.ExtInfo((int)Event.ACC_LOGIN_SUCCESS, "LogIn success", new { UserName = User.UserName, CompanyId = model.CompanyId, Culture = model.Culture });

            return Json(GenericResponseViewModel.SuccessObj(new { ReturnUrl = returnUrl }));

        }

    }
Run Code Online (Sandbox Code Playgroud)

验证过程在OnValidationIdentity中定义,我没有做太多定制.当validationInterval经过(...或更好地表示在一半到validationInterval)时,身份会重新生成并且自定义声明会丢失.

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),

            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                   validateInterval: TimeSpan.FromMinutes(1d),
                   regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))

            },
            /// TODO: Expire Time must be reduced in production do 2h
            ExpireTimeSpan = TimeSpan.FromDays(100d),
            SlidingExpiration = true,
            CookieName = "RMC.AspNet",
        });
Run Code Online (Sandbox Code Playgroud)

我想我应该如何能够将当前的声明传递给GenerateUserIdentityAsync,以便我可以重新添加自定义Clims,但我不知道如何.

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, string authenticationType)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
        // Add custom user claims here
        // ????????????????????????????

        return userIdentity;
    }
Run Code Online (Sandbox Code Playgroud)

任何帮助表示赞赏.

谢谢

l.r*_*ndi 8

问题解决了(似乎),我发布我的解决方案,因为我找不到可能适当的答案,我认为它可能对其他人有用.

在MVC5中Owin身份的regenerateIdentityCallback中的重用声明问题的答案中找到了正确的轨道

我刚刚修改了一些代码,因为在我的情况下UserId是字符串类型而不是Guid.

这是我的代码:

在Startup.Auth.cs中

 app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),

            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  

                //OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                //   validateInterval: TimeSpan.FromMinutes(1d),
                //   regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))

                OnValidateIdentity = context => SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, string>(
                   validateInterval: TimeSpan.FromMinutes(1d),
                   regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager, context.Identity),
                   getUserIdCallback: (ci) => ci.GetUserId()).Invoke(context)

            },
            /// TODO: Expire Time must be reduced in production do 2h
            //ExpireTimeSpan = TimeSpan.FromDays(100d),
            ExpireTimeSpan = TimeSpan.FromMinutes(2d),
            SlidingExpiration = true,
            CookieName = "RMC.AspNet",
        });
Run Code Online (Sandbox Code Playgroud)

注意:请注意,在我的示例中,ExpireTimeSpanvalidateInterval非常简短,因为此处的目的是为了测试目的而导致最多的frequest重新验证.

在IdentityModels.cs中,GenerateUserIdentityAsync的重载将负责将所有自定义声明重新附加到Identity.

    /// Generates user Identity based on Claims already defined for user.
    /// Used fro Identity re validation !!!
    /// </summary>
    /// <param name="manager"></param>
    /// <param name="CurrentIdentity"></param>
    /// <returns></returns>
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Re validate existing Claims here
        userIdentity.AddClaims(CurrentIdentity.Claims);


        return userIdentity;
    }
Run Code Online (Sandbox Code Playgroud)

有用.不确定它是否是最佳解决方案,但如果有人有更好的方法,请随时改进我的答案.

谢谢.

洛伦佐

附录

使用它一段时间后,我发现如果与@ Html.AntiForgeryToken()一起使用,GenerateUserIdentityAsync(...)中实现的内容可能会产生问题.我之前的实现将在每次重新验证时继续添加现有的声明.这会混淆引发错误的AntiForgery逻辑.为了防止我以这种方式重新实现它:

    /// <summary>
    /// Generates user Identity based on Claims already defined for user.
    /// Used fro Identity re validation !!!
    /// </summary>
    /// <param name="manager"></param>
    /// <param name="CurrentIdentity"></param>
    /// <returns></returns>
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Re validate existing Claims here
        foreach (var Claim in CurrentIdentity.Claims) {
            if (!userIdentity.HasClaim(Claim.Type, Claim.Value))
                userIdentity.AddClaim(new Claim(Claim.Type, Claim.Value));
        }

        return userIdentity;
    }

}
Run Code Online (Sandbox Code Playgroud)

附录2

我不得不进一步完善我的机制,因为我的previosu ADDENDUM会在一些特殊情况下导致重新验证期间描述的相同问题.当前最终解决方案的关键是添加我可以清楚识别的声明,并在重新验证期间仅添加那些声明,而不必尝试区分本地的(ASP身份)和我的.所以现在在LogIn中我添加以下自定义声明:

 User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CustomClaim.CultureUI", ClaimValue = UserProfile.CultureUI });
 User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CustomClaim.CompanyId", ClaimValue = model.CompanyId });
Run Code Online (Sandbox Code Playgroud)

请注意声明类型,现在以"CustomClaim."开头.

然后在重新验证中,我执行以下操作:

  public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Re validate existing Claims here
        foreach (var Claim in CurrentIdentity.FindAll(i => i.Type.StartsWith("CustomClaim.")))
        {
            userIdentity.AddClaim(new Claim(Claim.Type, Claim.Value));

            // TODO devo testare perché va in loop la pagina Err500 per cui provoco volontariamente la duplicazioen delle Claims
            //userIdentity.AddClaims(CurrentIdentity.Claims);

        }

        return userIdentity;
    }
Run Code Online (Sandbox Code Playgroud)

userIdentity不包含自定义声明,而CurrentIdentity确实包含两者,但我必须"重新附加"到当前标识的唯一一个是我的自定义声明.

到目前为止它工作正常,所以我将此标记为答案.

希望能帮助到你 !

洛伦佐