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

Custom Claims lost on Identity re validation

我正在实施 Asp.NET 具有身份 2.x 身份验证和授权模型的 MVC 应用程序。

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

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

        }

    }

验证过程在 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",
        });

我想我应该了解如何将当前声明传递给 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;
    }

感谢任何帮助。

谢谢

问题已解决(看起来),我 post 我的解决方案,因为我还没有找到可能合适的答案,我认为它可能对其他人有用。

Reuse Claim in regenerateIdentityCallback in Owin Identity in MVC5

问题的答案中找到了正确的轨道

我刚刚修改了一些代码,因为我的 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",
        });

注意:请注意,在我的示例中,ExpireTimeSpanvalidateInterval 非常短,因为这里的目的是为了测试目的引起最频繁的重新验证请求。

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

    /// 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;
    }

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

谢谢。

洛伦佐

附录

使用一段时间后,我发现 GenerateUserIdentityAsync(...) 中实现的内容如果与 @[=78 结合使用可能会出现问题=]()。我以前的实现会在每次重新验证时继续添加已经存在的声明。这混淆了抛出错误的反伪造逻辑。为了防止我以这种方式重新实现它:

    /// <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;
    }

}

附录 2

我必须进一步完善我的机制,因为我之前的附录在某些特殊情况下会导致重新验证期间描述的相同问题。 当前最终解决方案的关键是添加我可以清楚识别的声明并仅添加重新验证期间的声明,而不必尝试区分原生声明(ASP身份)和我的。 所以现在在登录过程中我添加了以下自定义声明:

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

注意现在以 "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;
    }

userIdentity 不包含自定义声明,而 CurrentIdentity 包含两者,但我必须 "re attach" 当前身份的唯一一个是我的自定义声明。

到目前为止一切正常,所以我将其标记为答案。

希望对您有所帮助!

洛伦佐

哦,天哪,我已经厌倦了尝试让它工作,我只是修改了 SecurityStampValidator 以获取一个上下文,我可以从中提取身份以在我的用户 class 中相应地更新。据我所知,没有办法直接扩展它。从 manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); 更新声明对使用 GenerateUserIdentityAsync

没有影响
var validator = MySecurityStampValidator
    .OnValidateIdentity<ApplicationUserManager, ApplicationUser, Guid>(
        validateInterval: TimeSpan.FromSeconds(2),
        regenerateIdentityCallback: (manager, user, claims) => user.UpdateUserIdentityAsync(claims),
        getUserIdCallback: (id) => id.GetUserGuid());

var cookieAuthenticationOptions = new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    Provider = new CookieAuthenticationProvider
    {
        // Not called on signin
        OnValidateIdentity = validator
    }
};

然后复制了 owin class 但添加了将传递到我的 regenerateIdentityCallback

的上下文
static class MySecurityStampValidator
{
    public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser, TKey>(
        TimeSpan validateInterval,
        Func<TManager, TUser, ***ClaimsIdentity***, Task<ClaimsIdentity>> regenerateIdentityCallback,
        Func<ClaimsIdentity, TKey> getUserIdCallback)
        where TManager : UserManager<TUser, TKey>
        where TUser : class, IUser<TKey>
        where TKey : IEquatable<TKey>
    {

...... 

然后在我的用户中我只是

public override async Task<ClaimsIdentity> UpdateUserIdentityAsync(ClaimsIdentity userIdentity)
{
    userIdentity.RemoveClaim(CustomClaimTypes.CLAIM1);
    userIdentity.RemoveClaim(CustomClaimTypes.CLAIM2);
    
    if (Access1Service.GetService().UserHasAccess(Id))
    {
        userIdentity.AddClaim(new Claim(CustomClaimTypes.CLAIM1, "1"));
    }

    if (Access2Service.GetService().UserHasAccess(Id))
    {
        userIdentity.AddClaim(new Claim(CustomClaimTypes.CLAIM2, "1"));
    }

    return userIdentity;
}