在 GenerateUserIdentity 中添加的声明在登录后消失



我创建了一个 C# 类库,用于为使用相同用户架构的多个 Web 应用程序实现 ASP.NET Identity。我正在User类的GenerateIdentity方法中向用户的身份添加自定义声明:

private ClaimsIdentity GenerateIdentity(UserManager userManager)
{
var identity = userManager.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaims(
new[]
{
new Claim(nameof(UserId), UserId.ToString()),
new Claim(ClaimTypes.Role, UserType.ToString()),
new Claim(nameof(FullName), FullName),
new Claim(nameof(FirmName), FirmName),
new Claim(nameof(HasDebit), HasDebit.ToString()),
new Claim(nameof(ShouldShowAssistants), ShouldShowAssistants.ToString()),
new Claim(nameof(IsSuspended), IsSuspended.ToString()),
new Claim(nameof(FirmId), FirmId.ToString()), 
});
if (IsSelfAdmin)
{
identity.AddClaim(new Claim(ClaimTypes.Role, SelfAdminRole));
}
return identity;
}
public Task<ClaimsIdentity> GenerateIdentityAsync(UserManager userManager)
{
return Task.FromResult(GenerateIdentity(userManager));
}

在登录过程中方法返回之前,声明确实存在于标识对象上。但是,在用户登录并重定向请求后,所有自定义声明都将消失。这是我在其中一个 Web 应用程序(较旧的 VB.NET WebForms 应用程序(中的登录代码:

Dim signInManager = Context.GetOwinContext().GetUserManager(Of SignInManager)
Dim result = signInManager.PasswordSignIn(UserID_TB.Text, PW_TB.Text, RememberMe_CB.Checked, False)
If result = SignInStatus.Failure
SetMessage("Failed")
Return
End If
Response.Redirect("profile.aspx")

登录成功,但在配置文件.aspx页上,User IPrincipal 属性只有默认的 4 个声明。

应用程序启动时可能相关的代码:

app.UseCookieAuthentication(New CookieAuthenticationOptions With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.Provider = New CookieAuthenticationProvider With {
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity (Of UserManager, User)(
TimeSpan.FromSeconds(0),
Function(manager, user) user.GenerateIdentityAsync(manager))
}
})

根据此应用程序的规范,验证间隔为 0。

如果任何其他代码有用,请告诉我。

更新

我发现第二次重定向后一切都按预期工作。当登录重定向到配置文件时,声明将丢失,但如果配置文件再次重定向,则声明将在那里。我真的很困惑为什么会这样。

更新:实际解决方案

这个问题实际上是因为我打电话给UserManager.CreateIdentity而不是User.GenerateIdentity。我的SecurityStamp.OnValidateIdentity使用了正确的登录代码,但没有使用我的登录代码。这应该非常明显,因为User.GenerateIdentity在添加所有自定义声明之前包含对UserManager.CreateIdentity的调用,但它在我脑海中飞了一段时间。

我的登录代码现在是这样的:

Dim userManager = Context.GetOwinContext().GetUserManager(Of UserManager)
Dim authenticatingUser = userManager.Find(UserID_TB.Text, PW_TB.Text)
If authenticatingUser Is Nothing
SetMessage("Failed")
Return
End If
userManager.UpdateSecurityStamp(authenticatingUser.Id)
Dim userIdentity = authenticatingUser.GenerateIdentityAsync(userManager).Result
Dim authenticationManager = HttpContext.Current.GetOwinContext().Authentication
authenticationManager.SignIn(
New AuthenticationProperties With {
.IsPersistent = RememberMe_CB.Checked
},
userIdentity)
Response.Redirect("profile.aspx", False)

我还写了这个来处理刷新的用户声明:

Public Shared Sub RefreshSignIn()
Dim authenticationManager = HttpContext.Current.GetOwinContext().Authentication
authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie)
Dim userManager = HttpContext.Current.GetOwinContext().GetUserManager(Of UserManager)
Dim user = userManager.FindById(HttpContext.Current.User.Identity.GetUserId())
Dim userIdentity = user.GenerateIdentityAsync(userManager).Result
authenticationManager.SignIn(userIdentity)
End Sub

源语言

我通过在登录后添加额外的重定向来解决问题。对于我提供的示例应用程序,我们总是重定向到配置文件,因此我将配置文件的Page_Load事件处理程序更改为:

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not User.IsAuthenticated()
' The user actually isn't authenticated; send them to login
Response.Redirect("login.aspx")
End If
If User.GetSomeClaimThatShouldBeHere() Is Nothing
' The second redirect that somehow makes this work
Response.Redirect("profile.aspx")
End If
' Do the rest of the page load that depends on the user claims
End Sub

如果我们在登录后还没有使用捕获所有页面,我们可以创建一个虚拟页面来处理重定向逻辑,如下所示:

Public Class LoginRedirect
Inherits Page
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not User.IsAuthenticated()
' The user actually isn't authenticated; send them to login
Response.Redirect("login.aspx")
End If
If User.GetSomeClaimThatShouldBeHere() Is Nothing
' The second redirect that somehow makes this work
Response.Redirect("loginredirect.aspx")
End If
If Not String.IsNullOrWhiteSpace(Request.QueryString("returnUrl"))
' Redirect to a return URL you've been passing around
Response.Redirect(Request.QueryString("returnUrl"))
End If
' Do some other logic to determine where the user (with all their claims available) needs to go
End Sub
End Class

我不会在一段时间内在接受的答案上标记这一点,以防有人想出比做看似多余的重定向更好的解决方案。

最新更新