我有一个MVC站点,该站点允许使用Forms登录名和Windows身份验证登录。我使用一个自定义的MembershipProvider,该成员针对Active Directory,用于CSRF保护的System.Web.Helpers AntiForgery类以及Owin Cookie身份验证中间件对用户进行身份验证。
登录期间,一旦用户通过了针对Active Directory的身份验证,我将执行以下操作:
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(StringConstants.ApplicationCookie);
var identity = new ClaimsIdentity(StringConstants.ApplicationCookie,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
if(HttpContext.Current.User.Identity is WindowsIdentity)
{
identity.AddClaims(((WindowsIdentity)HttpContext.Current.User.Identity).Claims);
}
else
{
identity.AddClaim(new Claim(ClaimTypes.Name, userData.Name));
}
identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Active Directory"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userData.userGuid));
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);
我的SignOut函数如下所示:
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(StringConstants.ApplicationCookie);
通过jQuery.ajax请求执行登录。成功后,
Window.location
将更新到站点的主页。使用Forms和
IntegratedWindowsAuthentication
(IWA)登录都可以,但是使用IWA登录时遇到了问题。这是发生了什么:此时的
User.Identity
的类型为WindowsIdentity
,且AuthenticationType
设置为Negotiate
,而不是我期望的那样,它是在上面的ClaimsIdentity
方法中创建的SignIn
。该站点通过在 View 中调用
@AntiForgery.GetHtml()
为用户准备主页。这样做是为了使用登录用户的详细信息创建一个新的AntiForgery token 。 token 是使用WindowsIdentity
ClaimsIdentity
到达!因此,第一个到达的POST
请求不可避免地会导致AntiForgeryException
,在该位置它发送的防伪 token 是“针对其他用户”。 刷新页面会使主页加载
ClaimsIdentity
并允许POST
请求起作用。其次,相关的问题:在刷新后的任何时候,只要一切正常运行,
POST
请求就可以使用WindowsIdentity
而不是ClaimsIdentity
到达,再次抛出AntiForgeryException
。我觉得我要么缺少有关
User.Identity
的东西,要么在登录过程中做错了什么……有什么想法吗?注意:设置
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
允许AntiForgery.Validate
操作成功,无论是否收到WindowsIdentity
或ClaimsIdentity
,但如MSDN所述:没有更多的解释,我不知道实际上在这里打开了哪些安全漏洞,因此不愿意将其用作解决方案。
最佳答案
原来的问题是ClaimsPrincipal支持多个身份。如果您处于多个身份的情况下,它将自行选择一个。我不知道是什么决定了IEnumerable中身份的顺序,但是无论如何,它显然必然导致用户 session 生命周期中的顺序恒定。
正如asp.net/Security git的Issues部分NTLM and cookie authentication #1467所述:
和
为此,请创建一个带有签名的静态方法:
static ClaimsIdentity MyPrimaryIdentitySelectorFunc(IEnumerable<ClaimsIdentity> identities)
该方法将用于遍历
ClaimsIdentity
的列表并选择您喜欢的方法。然后,在 Global.asax.cs 中将此方法设置为
PrimaryIdentitySelector
,如下所示:System.Security.Claims.ClaimsPrincipal.PrimaryIdentitySelector = MyPrimaryIdentitySelectorFunc;
我的
PrimaryIdentitySelector
方法最终看起来像这样:public static ClaimsIdentity PrimaryIdentitySelector(IEnumerable<ClaimsIdentity> identities)
{
//check for null (the default PIS also does this)
if (identities == null) throw new ArgumentNullException(nameof(identities));
//if there is only one, there is no need to check further
if (identities.Count() == 1) return identities.First();
//Prefer my cookie identity. I can recognize it by the IdentityProvider
//claim. This doesn't need to be a unique value, simply one that I know
//belongs to the cookie identity I created. AntiForgery will use this
//identity in the anti-CSRF check.
var primaryIdentity = identities.FirstOrDefault(identity => {
return identity.Claims.FirstOrDefault(c => {
return c.Type.Equals(StringConstants.ClaimTypes_IdentityProvider, StringComparison.Ordinal) &&
c.Value == StringConstants.Claim_IdentityProvider;
}) != null;
});
//if none found, default to the first identity
if (primaryIdentity == null) return identities.First();
return primaryIdentity;
}
[编辑]
现在,这还远远不够,因为当
PrimaryIdentitySelector
列表中只有一个Identity
时,Identities
似乎没有运行。这在登录页面中引起问题,有时浏览器在加载页面时会通过WindowsIdentity,但在登录请求中却不会传递WindowsIdentity {激怒叹气}。为了解决这个问题,我最终为登录页面创建了ClaimsIdentity,然后按照this SO question中的描述手动覆盖了线程的Principal。这会导致Windows身份验证出现问题,因为
OnAuthenticate
不会发送401来请求Windows身份。要解决此问题,您必须注销登录身份。如果登录失败,请确保重新创建登录用户。 (您可能还需要重新创建CSRF token )