我创建了一个 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
我不会在一段时间内在接受的答案上标记这一点,以防有人想出比做看似多余的重定向更好的解决方案。