如何将 WindowsIdentity 转换为 NetworkCredential?

Posted

技术标签:

【中文标题】如何将 WindowsIdentity 转换为 NetworkCredential?【英文标题】:How to convert WindowsIdentity to a NetworkCredential? 【发布时间】:2013-03-07 23:37:41 【问题描述】:

我们如何将WindowsIdentity 转换为NetworkCredential?我正在测试我的 WCF 服务以验证匿名呼叫者是否被阻止。为此,我想做类似的事情:

myProxy.ClientCredentials.Windows.ClientCredential = foo(WindowsIdentity.GetAnonymous());

其中foo 是将WindowsIdentity 转换为NetworkCredential 的方法

【问题讨论】:

这个 SO Answer 似乎会回答你的问题:***.com/questions/2063408/… 不,它没有。一方面,WindowsIdentity.GetAnonymous().Impersonate() 给出了一个例外。这也记录在msdn.microsoft.com/en-us/library/w070t6ka.aspx。 InvalidOperationException 匿名身份尝试执行模拟。看起来没有办法做问题中提出的问题。那么如何测试一项服务是否不允许匿名用户进入呢? 您的问题标题是在将 WindowsIdentity 转换为 NetworkCredential 之后,但是在多次重新阅读您的问题后,您只需要它来获得“匿名”用户吗?将客户端凭据设置为 null 就可以了,会发布一些代码 @EdmundYeung99,将客户端凭据设置为 null 将不起作用。你可以自己试试。 好的,但这对我们有用。很高兴你找到了答案:) 【参考方案1】:

一般来说 foo 是不存在的。由于 NetworkCredential 是比 WindowsIdentity 更广泛的对象。也就是说,我可以在需要 WindowsIdentity 的地方使用 NetworkCredential,但反之则不行。

原因是出于安全考虑。

NetworkCredential 表示您可以获取此身份并在另一台计算机上使用其关联的凭据。

这意味着

    您拥有用户凭据,而不仅仅是其身份。 这组凭据非常适合模拟,而不仅仅是本地访问。

我将假设凭据来自另一台机器(由于 WCF)。再次出于安全原因,当进入这台机器时,该网络凭据已转换为 LocalCredential(除非我们谈论的是委派而不是模拟)。客户端计算机授予在服务器计算机上使用其凭据的权利,并且仅在服务器计算机上。

如果您想取回 NetworkCredential,您需要做一些称为委托的事情,因为这就是所谓的多跳问题。这涉及到 Kerberos,一只黑社会的三头恶犬。您不想处理 Kerberos。

一般来说,默认情况下,WCF 代理不会在调用时发送凭据。一般需要设置ClientCredentials或者设置

proxy.UseDefaultCredentials = true

不提供任何一个通常意味着没有凭据,因此是匿名身份验证。

【讨论】:

【参考方案2】:

回答我自己的问题: 无法将 WindowsIdentity 转换为 NetworkCredential。要测试匿名调用者是否被阻止,请使用null session token 模拟当前线程,然后调用 WCF 服务。注意:不要使用WindowsIdentity.GetAnonymous。这种方法是没用的(猜想它被错误地实现了,并且从未被纠正过)。使用空会话令牌模拟当前线程的代码(未进行错误处理):

    public static class Helper
    
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern IntPtr GetCurrentThread();

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern bool ImpersonateAnonymousToken(IntPtr handle);

        public static void ImpersonateAnonymousUser()
                    
            ImpersonateAnonymousToken(GetCurrentThread());
        
    

        static string ToString(IIdentity identity)
        
            return string.Format("0 1 2", identity.Name, identity.IsAuthenticated, identity.AuthenticationType); 
        

        static void Main(string[] args)
                    
            Console.WriteLine(ToString(WindowsIdentity.GetCurrent()));
            Helper.ImpersonateAnonymousUser();
            Console.WriteLine(ToString(WindowsIdentity.GetCurrent()));
        

输出:

my machine\me True NTLM
NT AUTHORITY\ANONYMOUS LOGON False

针对 Edmund 的评论,将 proxy.ClientCredentials.Windows.ClientCredential 设置为 null 不会达到预期效果 - 以匿名用户身份发出请求。这是我的完整测试代码及其输出:

服务代码:

public class Service1 : IService1
    
        // note that if client is not authenticated, this code will never get a chance to execute
        // exception will happen before that
        // therefore there is no need to decorate this method with a
        // [PrincipalPermission(SecurityAction.Demand, Authenticated=true)] attribute        
        public string GetData()
        
            try
            
                var identity = Thread.CurrentPrincipal.Identity;
                return string.Concat(identity.Name, ",", identity.IsAuthenticated, ",", identity.AuthenticationType);
            
            catch (Exception e)
            
                return string.Concat(e.Message, "\\r\\n", e.StackTrace);
            
        
    

服务配置:

<services>      
      <service name="WcfService1.Service1">
        <host>
          <baseAddresses>
            <add baseAddress="http://mymachine/Service1/" />            
          </baseAddresses>                    
        </host>
        <endpoint address="Service1"
                  binding ="customBinding"
                  bindingConfiguration="myHttpBinding"
                  contract="WcfService1.IService1">          
        </endpoint>          
      </service>
    </services>
    <bindings>      
      <customBinding>
        <binding name="myHttpBinding">
            <reliableSession/>          
            <binaryMessageEncoding />          
            <httpTransport maxBufferSize="2147483647"
                           maxReceivedMessageSize="2147483647"
                           authenticationScheme="IntegratedWindowsAuthentication" />
          </binding>
      </customBinding>
    </bindings>

客户端代码:

static void MakeRequest()
        
            try
            
                using (var svc = new Service1Client())
                
                    Console.WriteLine(svc.GetData());
                
            
            catch (Exception e)
            
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
            
        

        static void Test3()
                    
            Console.WriteLine("using 0", ToString(WindowsIdentity.GetCurrent()));
            MakeRequest();
            Console.WriteLine();

            Console.WriteLine("setting svc.ClientCredentials.Windows.ClientCredential to null...");
            try
            
                using (var svc = new Service1Client())
                
                    svc.ClientCredentials.Windows.ClientCredential = null; 
                    Console.WriteLine(svc.GetData());
                
            
            catch (Exception e)
            
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
            
            Console.WriteLine();

            ImpersonateAnonymousUser();
            Console.WriteLine("using 0", ToString(WindowsIdentity.GetCurrent()));
            MakeRequest();
            Console.WriteLine();
        

客户端配置:

<bindings>
            <customBinding>
                <binding name="CustomBinding_IService1">
                    <reliableSession />
                    <binaryMessageEncoding />
                    <httpTransport authenticationScheme="Negotiate" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="mymachine/Service1/Service1.svc/Service1"
                binding="customBinding" bindingConfiguration="CustomBinding_IService1"
                contract="ServiceReference1.IService1" name="CustomBinding_IService1">
                <identity>
                    <servicePrincipalName value="host/mymachine" />
                </identity>
            </endpoint>
        </client>
      <behaviors>
        <endpointBehaviors>
          <!-- this setting protects the client by prohibiting the service to assume identity of client
          via imperonation and/or delegation and then doing bad things -->
          <behavior name="ImpersonationBehavior">
            <clientCredentials>
              <windows allowedImpersonationLevel="Identification"/>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>

输出:

using mymachine\me True Negotiate
mymachine\me,True,Negotiate

setting svc.ClientCredentials.Windows.ClientCredential to null...
mymachine\me,True,Negotiate

using NT AUTHORITY\ANONYMOUS LOGON False
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be
 used for communication because it is in the Faulted state.

Server stack trace:
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage req
Msg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgDa
ta, Int32 type)
   at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.
Close(TimeSpan timeout)
   at System.ServiceModel.ClientBase`1.Close()
   at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
   at TestClient.Program.MakeRequest()

【讨论】:

【参考方案3】:

我们有一组测试,我们使用这个辅助方法:

public static ResponseType CallService(RequestType requestBody, NetworkCredential credential)

    ResponseType response;
    using (var channelFactory = new ChannelFactory<IMyService>("BasicHttpBinding_IMyService"))
    
        channelFactory.Credentials.Windows.ClientCredential = credential;

        var client = channelFactory.CreateChannel();
        var request = new MyRequest()
            
                MyService = requestBody
            ;
        response = client.MyService(request).MyResponse1;
        ((IClientChannel)client).Close();

        channelFactory.Close();
    
    return response;

在测试中的使用:

var requestBody = GetRequestBody();//another helper method
var credential = new NetworkCredential
    
        Domain = "MyDomain", 
        UserName = "MyUsername", 
        Password = "MyPassword"
    ;
var response = CallService(requestBody, credential);
//Assert various user credentials


var response = CallService(requestBody, null);
//Assert anonymous

【讨论】:

我也这样做了。使用这个我验证了 WCF 服务(或者更确切地说是 IIS)不会让密码错误的用户通过。但我想测试匿名用户是否通过。 您可以在将 null 传递给客户端凭据时测试匿名用户

以上是关于如何将 WindowsIdentity 转换为 NetworkCredential?的主要内容,如果未能解决你的问题,请参考以下文章

如何从Azure获取当前的WindowsIdentity

如何使用 WindowsIdentity 和 SQL Server 集成安全设置表单身份验证

WindowsIdentity winId = (WindowsIdentity)HttpContext.Current.User.Identity;如何使这个演员工作;目前它失败了

为啥 IIS 似乎将来自 AppPool 的一个请求的 WindowsIdentity 提升到我的用户帐户?

在 ASP.NET MVC 中将 FormsIdentity 替换为 WindowsIdentity

如何使用 IIS 从 Blazor 服务器获取 WindowsIdentity.RunImpersonated(token, action) 的 HttpContext(或 AccessToken)