User.Identity在ClaimsIdentity和WindowsIdentity

时间:2018-05-08 15:16:46

标签: c# asp.net-mvc owin claims-based-identity windows-identity

我有一个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登录时遇到了问题。这就是:

  1. 用户在登录页面上选择IWA并点击提交按钮。这将通过ajax请求发送到常规登录操作。
  2. 网站收到请求,看到"使用IWA"选项并重定向到相关操作。发送302响应。
  3. 浏览器自动处理302响应并调用重定向目标。
  4. 过滤器看到请求前往IWA登录操作,而User.Identity.IsAuthenticated == false。发送401响应。
  5. 浏览器自动处理401响应。如果用户尚未在浏览器中使用IWA进行身份验证,则会弹出一个弹出窗口(默认浏览器行为)。收到凭据后,浏览器会使用用户凭据执行相同的请求。
  6. 站点接收经过身份验证的请求,并模拟用户对Active Directory执行检查。如果用户通过了身份验证,我们将使用上面的代码完成SignIn。
  7. 将用户转发到该网站的主页。
  8. 网站收到加载主页面的请求。 这是有时出错的地方 此时的User.Identity类型为WindowsIdentityAuthenticationType设置为Negotiate,而 NOT 正如我所料,ClaimsIdentity在上面的SignIn方法中创建 该站点通过在视图中调用@AntiForgery.GetHtml()来为用户准备主页面。这样做是为了使用登录用户的详细信息创建新的AntiForgery令牌。令牌使用WindowsIdentity
  9. 创建
  10. 当主页面加载时,对服务器的ajax请求以ClaimsIdentity到达!因此,第一个POST到达请求不可避免地导致AntiForgeryException其中发送的防伪令牌为#34;对于其他用户"。
  11. 刷新页面会导致主页加载ClaimsIdentity并允许POST个请求生效。

    其次,相关问题:在刷新后的任何时候,一旦事情正常工作,POST请求可能会WindowsIdentity而不是{{}} 1}},再次抛出ClaimsIdentity

    • 任何特定的帖子请求,
    • 在任何特定时间后(可能是第一个/第二个请求,可能是第100个),
    • 在该会话期间第一次调用特定的帖子请求时,不是

    我觉得我错过了关于AntiForgeryException的内容或者我在登录过程中做错了什么......有什么想法吗?

    注意 :无论收到User.Identity还是AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;,设置AntiForgery.Validate都可以使WindowsIdentity操作成功,但正如MSDN上所述:

      

    设置此值时请小心。使用不当可以打开   应用程序中的安全漏洞。

    没有更多的解释,我不知道这里实际打开了哪些安全漏洞,因此我不愿意将其作为解决方案。

2 个答案:

答案 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设置为falseIISMiddleware也会用HttpContext.Features更新WindowsPrincipal,所以var windowsUser = HttpContext.Features.Get<WindowsPrincipal>();会在我的SSO上返回Windows用户页面。

现在一切正常,没有障碍,没有波动,什么也没有。 Forms base和Windows身份验证可以一起使用。

services.Configure<IISOptions>(opts =>
{
    opts.AutomaticAuthentication = false;
});