客户端 - 服务器身份验证 - 使用SSPI?

时间:2013-06-21 17:48:53

标签: c# .net windows-authentication sspi

我正在研究客户端 - 服务器应用程序,我希望客户端使用用户的登录凭据向服务器验证自身,但我不希望用户必须输入他们的用户名和密码。我当然不想负责安全处理密码。我只需要用户向我证明他们是他们所说的人,然后我的服务器就可以继续执行并随意授予/拒绝命令。

我的用户是域的一部分,因此我希望能够使用他们登录时创建的登录凭据。

我没有使用任何类型的网络服务,也不是我想要的。我控制客户端和服务器软件,两者都是用纯C#编写的,并使用好的插槽来完成工作。

我更喜欢用纯C#/。Net来做这件事,但我愿意使用不安全的C#和pinvokes到win32 API,如果这意味着我将完成工作。

我在Windows中读过一些关于SSPI的内容,但是我在黑暗中有点感觉,因为这种应用程序开发对我来说是新的。

有人知道怎么做吗? SSPI是这样的吗?如何在C#中使用SSPI?是否有.Net原生方式,以便我的代码可以保持可移植性?

5 个答案:

答案 0 :(得分:22)

更新

SSPI是正确的方法。 API并不难以使用,但确实需要一个体面的项目来包装到C#中。

在研究解决这个问题的必要部分的过程中,我写了一个项目,在.Net中提供SSPI。下面我将介绍与Windows SSPI API接口的基础知识,以便任何人都可以复制我的结果。如果您发现自己想在.Net中使用SSPI,我建议您使用我创建的项目来解决这个问题:

<强> NSspi - A .Net interface to the SSPI API

SSPI为您提供包含身份验证令牌的原始字节数组,然后您决定如何传输 - 无论是通过带有二进制格式消息的套接字,自定义XML通道,.Net Remoting,某种形式的WCF,heck,甚至是串行港口。你可以决定如何处理它们。使用SSPI,服务器可以对客户端进行身份验证,安全地识别客户端,甚至可以使用与客户端建立的安全上下文执行加密/签名等基本消息处理过程。

此处记录了SSPI API:SSPI API overview

具体看看以下功能:

典型的工作流程是每一方都将使用AcquireCredentialsHandle初始化其凭据。然后认证周期开始并按如下方式进行:

  • 客户端调用InitializeSecurityContext,不提供输入令牌,它以字节数组的形式返回输出令牌。 ISC返回&#39; ContinueNeeded&#39;表示身份验证周期未完成。
  • 客户端以任何方式将令牌发送到服务器。
  • 服务器将收到的令牌作为输入提供给AcceptSecurityContext,并生成自己的输出令牌。 ASC还返回&#39; ContinueNeeded&#39;表示身份验证 周期不完整。
  • 然后,服务器将其输出令牌发送给客户端。
  • 客户端提供服务器令牌作为InitializeSecurityContext的输入,InitializeSecurityContext返回新的输出令牌。
  • 客户端将新的输出令牌发送到服务器。
  • ...

此循环一直持续到客户端看到InitializeSecurityContext返回&#39; OK&#39;并且服务器看到AcceptSecurityContext返回&#39; OK&#39;。每个功能都可以返回&#39; OK&#39;并且仍然提供输出令牌(由非空返回指示),以指示它仍然必须将数据发送到另一端。这就是客户知道它的一半完成但服务器仍然不完整的方式;如果服务器在客户端之前完成,则反之亦然。哪一方首先完成(返回&#39; OK&#39;)取决于SSPI在引擎盖下使用的特定安全包,任何SSPI消费者都应该知道这一点。

上述信息应足以让任何人与SSPI系统连接,以便提供“Windows集成身份验证”。在他们的申请中并复制我的结果。

以下是我之前的回答,因为我学习了如何调用SSPI API。


我已经忘记了这个问题,并且几天前突发奇想地回到了这个问题。我确实需要在一两年内解决这个问题:)

有可能在.Net中,我正在开发一个我打算发布的.Net SSPI包装器。

我的工作是基于我发现的一些SSPI samples的工作。

该示例包含一个C ++ / CLI托管程序集,该程序集实现了SSPI API的必要部分(在从REMSSPI.exe文件中提取的文件夹Microsoft\Samples\Security\SSPI\SSPI中)。然后他们有两个UI,一个客户端应用程序和一个服务器应用程序,都是用C#编写的,它们使用这个API来执行SSPI身份验证。

UI使用.Net远程处理工具将它们绑定在一起,但如果我正确理解SSPI API,客户端和服务器需要交换的唯一信息包含包含安全上下文令牌数据的byte [] ,可以轻松集成到您想要的任何通信基础设施中;就我而言,是我自己设计的二进制协议。

关于让样本运行的一些注意事项 - 他们拥有“SSPI”和“SSPI”。图书馆资源,最好在VS 2005下编写,尽管我已经在2008年之前完成了它的工作; 2010或更高版本需要进行一些返工,因为它们使用的是不推荐使用的语言结构。您可能还需要修改属于您的平台SDK的头文件,因为它们使用const指针分配给非对象变量,而且我不知道让编译器开心的更好方法(我已经知道了)以前从未使用过C ++ / CLI。

它们在Microsoft \ Samples \ Security \ SSPI \ bin文件夹中包含已编译的SSPI dll。要使客户端/服务器二进制文件工作,您必须将该DLL复制到其bin目录,否则将失败的程序集解析。

总结一下:

  • 转到here下载REMSSPI.exe示例自解压缩zip。
  • 提取REMSSPI.exe文件(两次..)
  • 微软\样本\安全\ SSPI \
    • bin\ - 包含已编译的dll Microsoft.Samples.Security.SSPI.dll
    • SSPI\ - 包含dll的来源
    • Sample\ - 包含UI源代码
      • bin\ - 包含构建UI示例。在此处复制SSPI.dll文件并运行ControlPanel.Client.exeControlPanel.Server.exe

答案 1 :(得分:0)

尼古拉是对的;没有.NET本地方法来完成您正在做的事情(至少,不使用低级.NET套接字支持)。你当然可以潜入幕后做一些互操作黑魔术,但是如果客户端和服务器都在你的控制之下,你可能需要考虑稍微向上移动堆栈并使用更高级别的API,例如WCF,它就是具有.NET本机支持Windows集成身份验证。

根据您的问题和您描述的环境,您将能够使用NetTcpBinding,它提供高性能以及您正在寻找的身份验证/身份流的管道(它还提供了一种相当干净的方式使用ServiceAuthorizationManager类处理授权。在不知道您的应用程序/服务的细节的情况下,我不可能提供“如何”来实现您想要做的事情,但我可以point you at the docs有一个相当简单的例子。

答案 2 :(得分:0)

我对WindowsIdentity(这对授权有利)完全错了,因为我忘记了WCF处理了很多配置文件,端点安全和消息/传输安全性的事情。

你试过NegotiateStream吗?给定的示例似乎更符合您的需求:在允许任何读/写之前,它使用Kerberos进行身份验证。使用CredentialCache.DefaultNetworkCredentials应该可以避免查询密码。

答案 3 :(得分:0)

您可能会问:“服务器如何确认客户的身份?”

答案::如果所有握手的往返都可以成功完成,即都

  • InitializeSecurityContext 返回“确定”
  • AcceptSecurityContext 返回“确定”

这意味着已确认客户端的Windows登录凭据是真实的。

AcceptSecurityContext 输出CtxtHandle安全上下文(通过第6 个参数)。

此上下文句柄包括客户端的Windows登录用户名。服务器可以通过调用QueryContextAttributesEx获取客户端的Windows用户名:

SecPkgContext_NativeNames pinfo;
QueryContextAttributesEx(&m_securitycontext, SECPKG_ATTR_NATIVE_NAMES, &pinfo);

这将填充Native Names结构:

SecPkgContext_NativeNames 
{
   SEC_CHAR *sClientName;
   SEC_CHAR *sServerName;
}

pinfo.sClientName是客户端的实际登录用户名。

注意:先前的握手已经保证了安全上下文的真实性,因此服务器将认为pinfo.sClientName只是客户端的真实Windows用户名。

答案 4 :(得分:-2)

曾经尝试过使用WindowsIdentity吗? 纯C#/。Net,serializable和GetCurrent()返回执行帐户。


  

安全令牌
  用户向您的应用程序提供一组声明   捎带她的要求。在Web服务中,这些声明是   在SOAP信封的安全标头中携带。在一个   基于浏览器的Web应用程序,索赔通过HTTP POST到达   用户的浏览器,如果是会话,稍后可能会缓存在cookie中   是理想的。无论他们如何到达,他们都必须序列化   不知何故,这就是安全令牌的来源。安全令牌   是由发行人数字签名的一系列序列化的权利要求   权威。签名很重要 - 它可以让您保证   用户不只是构成一堆声明并将它们发送给您。   在低安全性情况下,不需要加密或   期望,您可以使用无符号令牌,但这不是我的情景   本文将重点关注。 WIF的核心功能之一是   创建和读取安全令牌的能力。 WIF和底层证券   .NET Framework中的管道处理所有加密密码   提升,并向您的申请提出一系列声明   可以阅读。

引自Windows Identity Foudation WhitePaper

您的主要问题是:

  

&#34;是否有纯C#/ .NET方式使用他们的登录凭据对用户进行身份验证?&#34;

WindowsIdentity&#34;是&#34;您的域控制器发出的身份验证令牌在我看来是目前最好的方法。


我在第一次发布时对WindowsIdentity知之甚少,但我也觉得这对你的问题和限制很有帮助。我已经做了很多阅读,最后来到了page WIF的引入是不言自明的,WindowsIdentity是一套新的.NET框架,专为基于Windows /基于角色的安全问题而设计。
SSPI,Kerberos是整个Windows身份验证过程的一部分,用户/机器/进程获取的登录令牌由域控制器授予,并且不能通过&#34;简单地获得#34;实例化一个新的WindowsIdentity对象。如果存在这种非法的异常,整个Windows安全模型(域名,UAC等)将会死亡。

这是一个(非常!)小控制台程序,如果你不是&#34; BUILTIN \ Administrateurs&#34;的一部分,它会引发异常。 (根据您自己的需要更改组名)。每当&#34;以管理员身份运行时,程序都会顺利完成 有一组非常大的权限,每个需求都是基于声明的(是xxx的身份标识?)

using System;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;

namespace WindowsIdentityTest
{
    class Program
    {
        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        static string SomeServerAction()
        { return "Authenticated users can access"; }

        [PrincipalPermission(SecurityAction.Demand, Role = "BUILTIN\\Administrateurs")]
        static string SomeCriticalServerAction()
        { return "Only Admins can access"; }

        static void Main(string[] args)
        {
            //This allows to perform security checks against the current Identity.   
            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

            try
            {
                Console.WriteLine(SomeServerAction());
                Console.WriteLine(SomeCriticalServerAction());

            }
            catch (SecurityException sec)
            {
                Console.WriteLine(string.Format("{0} : {1}\n------------\n{2}"
                    , sec.GetType()
                    , sec.Message
                    , sec.StackTrace));
            }
            catch (Exception ex)
            {
                Console.WriteLine("This shall not appen.");
            }
            Console.WriteLine("Press enter to quit.");
            Console.ReadLine();
        }
    }
}

我希望这会有所帮助。