我有一个MVC站点,允许使用Forms登录和Windows身份验证登录。我使用自定义MembershipProvider对Active Directory进行身份验证,使用System.Web.Helpers AntiForgery类进行CSRF保护,以及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
会更新到网站的主页面。
使用表单和IntegratedWindowsAuthentication
(IWA)登录都可以,但我在使用IWA登录时遇到了问题。这就是:
User.Identity
类型为WindowsIdentity
,AuthenticationType
设置为Negotiate
,而 NOT 正如我所料,ClaimsIdentity
在上面的SignIn
方法中创建
该站点通过在视图中调用@AntiForgery.GetHtml()
来为用户准备主页面。这样做是为了使用登录用户的详细信息创建新的AntiForgery令牌。令牌使用WindowsIdentity
ClaimsIdentity
到达!因此,第一个POST
到达请求不可避免地导致AntiForgeryException
其中发送的防伪令牌为#34;对于其他用户"。刷新页面会导致主页加载ClaimsIdentity
并允许POST
个请求生效。
其次,相关问题:在刷新后的任何时候,一旦事情正常工作,POST
请求可能会WindowsIdentity
而不是{{}} 1}},再次抛出ClaimsIdentity
。
我觉得我错过了关于AntiForgeryException
的内容或者我在登录过程中做错了什么......有什么想法吗?
注意 :无论收到User.Identity
还是AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
,设置AntiForgery.Validate
都可以使WindowsIdentity
操作成功,但正如MSDN上所述:
设置此值时请小心。使用不当可以打开 应用程序中的安全漏洞。
没有更多的解释,我不知道这里实际打开了哪些安全漏洞,因此我不愿意将其作为解决方案。
答案 0 :(得分:3)
原来问题是ClaimsPrincipal支持多个身份。如果您处于具有多个身份的情况,则会自行选择一个身份。我不知道是什么决定了IEnumerable中身份的顺序,但无论它是什么,它显然必然会在用户会话的生命周期中产生一个不变的顺序。
正如asp.net/Security git的问题部分所述,NTLM and cookie authentication #1467:
身份包含windows身份和cookie身份。
和
与
ClaimsPrincipals
类似,您可以设置一个名为static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity>
的{{1}},您可以使用该PrimaryIdentitySelector
来选择要使用的主要身份。
为此,请使用签名创建一个静态方法:
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但在登录请求中没有传递它{exasperated sigh}。要解决 this ,我最终为登录页面创建了一个ClaimsIdentity,然后手动覆盖了线程的Principal,如this SO question中所述。
这会导致Windows身份验证出现问题,因为OnAuthenticate
不会发送401来请求Windows身份验证。要解决 this ,您必须注销登录身份。如果登录失败,请确保重新创建登录用户。 (您可能还需要重新创建CSRF令牌)
答案 1 :(得分:0)
我不确定这是否有帮助,但这就是我为我解决的问题的方式。
当我添加Windows身份验证时,它开始在Windows和Claims身份之间波动。我注意到的是GET
请求获得ClaimsIdentity
,但POST
请求获得WindowsIdentity
。这非常令人沮丧,我决定调试并在DefaultHttpContext.set_User
处设置一个断点。 IISMiddleware
设置了User
属性,然后我注意到它具有一个AutomaticAuthentication
,默认设置为true
,该属性设置了User
属性。我将其更改为false,所以HttpContext.User
一直变成ClaimsPrincipal
,欢呼。
我的问题不是如何使用Windows身份验证。幸运的是,即使我将AutomaticAuthentication
设置为false
,IISMiddleware
也会用HttpContext.Features
更新WindowsPrincipal
,所以var windowsUser = HttpContext.Features.Get<WindowsPrincipal>();
会在我的SSO上返回Windows用户页面。
现在一切正常,没有障碍,没有波动,什么也没有。 Forms base和Windows身份验证可以一起使用。
services.Configure<IISOptions>(opts =>
{
opts.AutomaticAuthentication = false;
});