我使用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;
}
感谢任何帮助。
由于
答案 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;
}