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

时间:2016-10-06 13:20:24

标签: validation asp.net-mvc-5 identity claims-based-identity

我使用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 }));

        }

    }

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

感谢任何帮助。

由于

2 个答案:

答案 0 :(得分:8)

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

在问题Reuse Claim in regenerateIdentityCallback in Owin Identity in MVC5

的回答中找到了正确的曲目

我刚刚修改了一些代码,因为在我的情况下UserId是string类型而不是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",
        });

注意:请注意,在我的示例中, ExpireTimeSpan validateInterval 非常简短,因为此处的目的是导致最多的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;
    }

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

感谢。

洛伦佐

<强>附录

使用它一段时间后,我发现如果与 @ 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;
    }

}

ADDENDUM 2

我不得不进一步完善我的机制,因为我的preiosu 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 });

请注意声明类型,现在以“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确实包含两者,但我必须“重新附加”到当前标识的唯一一个是我的自定义声明。

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

希望它有所帮助!

洛伦佐

答案 1 :(得分:0)

哦,天哪,我已经厌倦了尝试使其正常工作,我只是修改了SecurityStampValidator以使其具有可以使Identity退出的上下文,以便在User类中进行相应的更新。据我所知,没有办法直接扩展它。使用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类,但向其添加上下文,该上下文将传递到我的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;
}