c# - User.Identity 在 ClaimsIdentity 和 WindowsIdentity 之间波动
问题描述
我有一个 MVC 站点,它允许使用表单登录和 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
更新到站点的主页。
使用 Forms 和IntegratedWindowsAuthentication
(IWA) 登录都可以,但是使用 IWA 登录时遇到了问题。这就是发生的事情:
- 用户在登录页面上选择 IWA 并点击提交按钮。这通过 ajax 请求发送到常规登录操作。
- 站点收到请求,看到“使用 IWA”选项并重定向到相关操作。发送 302 响应。
- 浏览器自动处理 302 响应并调用重定向目标。
- 过滤器看到请求指向 IWA 登录操作,并且 User.Identity.IsAuthenticated == false。发送 401 响应。
- 浏览器会自动处理 401 响应。如果用户尚未在浏览器中使用 IWA 进行身份验证,他们会收到一个弹出窗口来执行此操作(默认浏览器行为)。收到凭据后,浏览器会使用用户凭据执行相同的请求。
- 该站点接收经过身份验证的请求并模拟用户对 Active Directory 执行检查。如果用户通过身份验证,我们使用上面的代码完成登录。
- 用户被转发到站点的主页。
- 该站点收到加载主页的请求。这就是事情有时会出错的地方。
此时User.Identity
的类型WindowsIdentity
设置AuthenticationType
为Negotiate
,而不是我所期望的,在上面的方法中ClaimsIdentity
创建。该站点通过调用视图 为用户准备主页。这样做是为了使用登录用户的详细信息创建一个新的 AntiForgery 令牌。令牌是用SignIn
@AntiForgery.GetHtml()
WindowsIdentity
- 当主页加载时,向服务器发出的 ajax 请求以
ClaimsIdentity
! 因此,第一个POST
到达的请求不可避免地会导致AntiForgeryException
它发送的防伪令牌是“为不同的用户”。
刷新页面会导致主页加载ClaimsIdentity
并允许POST
请求运行。
第二,相关的问题:在刷新后的任何时候,一旦事情正常工作,POST
请求可能会到达WindowsIdentity
而不是到达ClaimsIdentity
,再次抛出AntiForgeryException
.
- 这不是任何特定的帖子请求,
- 它不是在任何特定的时间之后(可能是第一个/第二个请求,可能是百分之一),
- 在该会话期间,不一定第一次调用特定的发布请求。
我觉得我要么User.Identity
在登录过程中遗漏了一些东西,要么我做错了什么......有什么想法吗?
注意:设置AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
允许AntiForgery.Validate
操作成功,无论是否WindowsIdentity
收到ClaimsIdentity
,但如 MSDN 所述:
设置此值时要小心。使用不当可能会在应用程序中打开安全漏洞。
没有更多的解释,我不知道这里实际上打开了哪些安全漏洞,因此我不愿意将其用作解决方案。
解决方案
原来问题是 ClaimsPrincipal 支持多个身份。如果您处于具有多个身份的情况,它会自行选择一个。我不知道是什么决定了 IEnumerable 中身份的顺序,但无论它是什么,它显然都必然导致用户会话生命周期中的恒定顺序。
如 asp.net/Security git 的问题部分所述,NTLM 和 cookie 身份验证 #1467:
身份包含 Windows 身份和 cookie 身份。
和
看起来
ClaimsPrincipals
您可以设置一个static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity>
调用PrimaryIdentitySelector
,您可以使用它来选择要使用的主要身份。
为此,请使用签名创建一个静态方法:
static ClaimsIdentity MyPrimaryIdentitySelectorFunc(IEnumerable<ClaimsIdentity> identities)
此方法将用于遍历ClaimsIdentity
s 列表并选择您喜欢的一个。
然后,在您的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
似乎没有运行。这会导致登录页面出现问题,有时浏览器会在加载页面时传递 WindowsIdentity,但不会在登录请求中传递它{exasperated sigh}。为了解决这个问题,我最终为登录页面创建了一个 ClaimsIdentity,然后手动覆盖线程的 Principal,如此 SO question中所述。Identity
Identities
这会导致 Windows 身份验证出现问题,因为OnAuthenticate
不会发送 401 来请求 Windows 身份。要解决此问题,您必须注销登录身份。如果登录失败,请确保重新创建登录用户。(您可能还需要重新创建 CSRF 令牌)
推荐阅读
- c# - 我的服务从哪里得到它的构造函数参数?
- sftp - 为什么 BizTalk 2016 会杀死自定义管道组件线程?
- postgresql - 使用 Java 和 Liquibase 在多个模式上应用迁移
- java - SharedPrefrences 中的 setTextColor
- laravel - production.ERROR:未定义变量:id Laravel 刀片
- c++ - 内存不足 C++
- c++ - 一维数组和三维数组使用一个模板
- angular - 尝试使用 webpack 构建 angular4 旧项目但失败
- c++ - 调整窗口大小时出现奇怪的伪影
- swift - 按顺序执行文本转语音