【问题标题】:Thread.CurrentPrincipal cannot be set to Forms Authentication principalThread.CurrentPrincipal 不能设置为表单身份验证主体
【发布时间】:2012-07-05 10:37:15
【问题描述】:

我有一个 WCF 服务,它托管在 ASP.NET MVC 应用程序中(如http://msdn.microsoft.com/en-us/library/aa702682.aspx 中所述)。部分 MVC 操作和 WCF 服务操作受到保护,我对两者都使用 ASP.NET 表单身份验证:

// protected MVC action
[Authorize]
public ActionResult ProtectedMvcAction(string args)

// protected WCF operation
[PrincipalPermission(SecurityAction.Demand, Role = "User")]
public void ProtectedWcfOperation(string args)

我的 WCF 客户端确保表单身份验证 .ASPXAUTH cookie 在每次 WCF 调用时传输到服务器。

这在很长一段时间内都非常有效。现在我正在使用SSL 证书向我的服务器添加HTTPS 加密。这需要我对 Web.config` 进行以下更改:

<basicHttpBinding>
  <binding name="ApiServiceBinding">
    <security mode="Transport">
      <transport clientCredentialType="None" />
    </security>
  </binding>
</basicHttpBinding>

服务被激活,客户端可以调用服务器操作。但是,受保护的服务器操作前面的[PrincipalPermission] 属性会突然阻止所有服务调用。我发现了以下内容:

  • 在 HTTP 情况下(没有&lt;security mode="Transport"&gt;),Thread.CurrentPrincipalHttpContext.Current.User 都设置为RolePrincipal 实例,FormsIdentity 实例在RolePrincipal.Identity 属性中。在这种情况下,一切正常。
  • 在 HTTPS 情况下(在 web.config 中使用 &lt;security mode="Transport"&gt;),属性 HttpContext.Current.User 仍设置为 RolePrincipal/FormsIdentity 组合。但是,Thread.CurrentPrincipal 属性突然被设置为WindowsPrincipal/WindowsIdentity 实例,这使得[PrincipalPermission] 属性抛出异常。

我尝试了以下方法:

  • AppDomain.CurrentDomain.SetPrincipalPolicy 更改为所有可能的值(在Global.asaxApplication_Start 中),但这并没有改变任何东西。
  • Application_PostAuthenticate 中设置属性Thread.CurrentPrincipal,但在Application_PostAuthenticate 和实际服务调用之间,ThreadCurrentPrincipal 再次更改为WindowsPrincipal

有什么提示吗?我做错了什么?

【问题讨论】:

    标签: asp.net https forms-authentication iprincipal


    【解决方案1】:

    这样就解决了:

    http://www.codeproject.com/Articles/304877/WCF-REST-4-0-Authorization-with-From-Based-authent

    我修改了这段代码以涵盖 Windows 和 Forms 端点以及相同的服务 - 这也有效 -

    public bool Evaluate( EvaluationContext evaluationContext, ref object state )
    {
        bool ret = false;
        // get the authenticated client identity
        HttpCookie formsAuth = HttpContext.Current.Request.Cookies[ ".MyFormsCookie" ];
        if( null != formsAuth )
        {
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt( formsAuth.Value );
            if( null != ticket )
            {
                GenericIdentity client = new GenericIdentity( ticket.Name, "Forms" );
    
                // set the custom principal
                CustomPrincipal p = new CustomPrincipal( client );
                p.RoleManagerProvider = "Internet";
                evaluationContext.Properties[ "Principal" ] = p;
    
                ret = true;
            }
        }
        else
        {
            CustomPrincipal p = new CustomPrincipal( HttpContext.Current.User.Identity );
            p.RoleManagerProvider = "Intranet";
            evaluationContext.Properties[ "Principal" ] = p;
    
            // assume windows auth
            ret = true;
    
        }
        return ret;
    }
    

    它会查找表单身份验证 cookie,如果不存在则尝试使用 Windows 身份验证。我还“翻转”了内部和外部的角色提供者

    这允许我将用户凭据从 Internet(通过转发 cookie)和 Intranet(使用 Windows 约束委派)传播到相同的内部服务。

    我在 config 中进行了配置(而不是示例中的代码),看起来不错。

    对于行为,它类似于:

     <behavior name="FormsPaymentsBehavior">
              <serviceAuthorization principalPermissionMode="Custom" >
                <authorizationPolicies>
                  <add policyType="FormsPolicy.AuthorizationPolicy,FormsPolicy" />
                </authorizationPolicies> 
              </serviceAuthorization>
    

    这用于两个端点,因为 FormsPolicy(上图)处理这两个端点,您不能为不同的端点指定不同的行为。

    绑定在适当的端点上强制执行 Windows 凭据握手:

    <basicHttpBinding>
            <binding name="WindowsHttpBinding">
              <security mode="TransportCredentialOnly">
                <transport clientCredentialType="Windows" />
              </security>
            </binding>
            <binding name="FormsHttpBinding" allowCookies="true">          
              <security mode="None">
                <transport clientCredentialType="None" />            
              </security>
            </binding>
          </basicHttpBinding>
    

    交通方式可以改成

    <security mode="Transport">
                <transport clientCredentialType="None" />
              </security>
    

    对于 https,它工作正常。

    对于您的自定义主体,我发现我必须明确调用角色管理器

    ...
    
    
    public bool IsInRole( string role )
            {
                RoleProvider p = Roles.Providers[ RoleManagerProvider ];
                return p.IsUserInRole( Identity.Name, role );
            }
    
            public String RoleManagerProvider { get; set; }
    

    ...

    我猜这是因为我不再使用任何 aspnet 兼容的东西。由于我正在根据我的身份验证类型翻转角色管理器,所以呵呵。

    【讨论】:

    • 感谢这解决了我在 WCF 服务构造函数中将线程主体设置为 HttpContext.Current.User 导致的间歇性 WCF“访问被拒绝”错误。
    【解决方案2】:

    我也遇到过这个问题,这里有另一个报告(和我的)。 http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8f424d4f-2f47-4f85-a6b0-00f7e58871f1/

    此线程指向创建自定义授权策略 (http://msdn.microsoft.com/en-us/library/ms729794.aspx) 和此代码项目文章 (http://www .codeproject.com/Articles/304877/WCF-REST-4-0-Authorization-with-From-Based-authent) 似乎准确地解释了如何为 FormsAuth 执行此操作-设置 evaluationContext.Properties["Principal"] = new根据 MS cmets 的 CustomPrincipal(client)。

    我还没有实现这个——我的“快速修复”是简单地恢复到一个普通的旧 asmx 服务——但我会“试一试”一段时间!

    如果您找到其他解决方案 - 请告诉我。

    【讨论】:

    • 谢谢,有时间我会检查你的解决方案。我自己的“快速修复”是删除PrincipalPermissionAttribute 并在HttpContext.Curren.User 上“手动”进行安全检查
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 1970-01-01
    • 2013-10-30
    相关资源
    最近更新 更多