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

Posted

技术标签:

【中文标题】客户端-服务器身份验证 - 使用 SSPI?【英文标题】:Client-server authentication - using SSPI? 【发布时间】:2013-06-18 22:42:49 【问题描述】:

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

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

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

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

我读过一些关于 Windows 中的 SSPI 的文章,但我有点摸不着头脑,因为这种应用程序开发对我来说是新事物。

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

【问题讨论】:

那么您是否考虑过使用 Windows 身份验证?这种类型的身份验证将使用用户的域用户名来验证对服务器的访问。连接字符串应类似于 Server=myServerAddress;Database=myDataBase;Trusted_Connection=True; 我认为没有 .NET 本地方式可以做到这一点。但是,MS 提供了一个示例,演示了如何使用 SSPI。它涉及创建一个托管 C++ 来处理 SSPI 并提供与 .NET 的接口。坦率地说,我无法在 Windows 8.1 上运行它,也没有考虑调试它,但这对你来说可能是一本好书。 msdn.microsoft.com/en-us/library/ms973911.aspx 当您在 .NET 中有更简单的解决方案时,我建议您重新考虑使用普通套接字的决定。 澄清一下:因为 WCF 可以在不使用 IIS/ASP.NET 但普通套接字的情况下做到这一点,我也应该能够这样做。 WCF 源代码(可通过参考源许可证获得)混乱且难以理解。 【参考方案1】:

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

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

InitializeSecurityContext 返回“OK” AcceptSecurityContext 返回“OK”

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

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

此上下文句柄包括客户端的 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用户名。

【讨论】:

【参考方案2】:

更新:

SSPI 是解决此问题的正确方法。 API 使用起来并不难,但确实需要一个相当大的项目来包装到 C# 中。

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

NSspi - A .Net interface to the SSPI API

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

此处记录了 SSPI API:SSPI API overview

具体看一下以下函数:

AcquireCredentialsHandle 获取某种形式的凭据(例如,当前用户的登录)的句柄。供服务器和客户端使用。 InitializeSecurityContext 客户端用于与服务器建立安全上下文。 AcceptSecurityContext 服务器用于与客户端建立安全上下文。

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

客户端调用 InitializeSecurityContext,不提供输入令牌,它以字节数组的形式返回输出令牌。 ISC 返回“ContinueNeeded”以指示身份验证周期未完成。 客户端通过它想要的任何方式将令牌发送到服务器。 服务器将接收到的令牌作为输入提供给 AcceptSecurityContext 并生成它自己的输出令牌。 ASC 还返回“ContinueNeeded”以指示身份验证 循环未完成。 然后服务器将其输出令牌发送到客户端。 客户端将服务器令牌作为输入提供给 InitializeSecurityContext,它返回新的输出令牌。 客户端将他的新输出令牌发送到服务器。 ...

这个循环一直持续到客户端看到 InitializeSecurityContext 返回“OK”并且服务器看到 AcceptSecurityContext 返回“OK”。每个函数都可以返回“OK”并且仍然提供一个输出标记(如非空返回所示),以表明它仍然必须向另一端发送数据。这就是客户端知道它的一半已经完成但服务器仍然不完整的方式;如果服务器在客户端之前完成,反之亦然。哪一方先完成(返回“OK”)取决于 SSPI 在后台使用的特定安全包,任何 SSPI 使用者都应该意识到这一点。

以上信息应该足以让任何人与 SSPI 系统交互,以便在他们的应用程序中提供“Windows 集成身份验证”并复制我的结果。

以下是我之前学习如何调用 SSPI API 时的回答。


我忘记了这个问题,巧合的是几天前一时兴起又回到了这个问题。不过我确实需要在一两年内解决这个问题:)

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

我的工作基于我从 Microsoft 找到的一些 SSPI samples。

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

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

关于让示例工作的一些注意事项 - 他们有“SSPI”库源,它最好在 VS 2005 下编译,尽管我已经让它在 2008 下工作; 2010 或更高版本需要一些返工,因为它们使用已弃用的语言结构。您可能还需要修改作为平台 SDK 一部分的头文件,因为它们使用 const 指针分配给 unconst 变量,而且我不知道让编译器满意的更好方法(我从未使用过 C++/ CLI 之前)。

它们确实在 Microsoft\Samples\Security\SSPI\bin 文件夹中包含已编译的 SSPI dll。要使客户端/服务器二进制文件正常工作,您必须将该 dll 复制到它们的 bin 目录中,否则将无法解决组装问题。

总结一下:

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

【讨论】:

是的。我也找到了那个样本。但更喜欢没有 DLL 的解决方案。如果您可以 p/invoke 这些 API 调用并获得现成的解决方案,您将获得积分。 那个 DLL 是一个托管的 .Net DLL,只是用 C++/CLI 编写的。由于我自己的原因,我正在用 C# 编写我自己的版本,但我可以很容易地使用那个版本。 嗯,好的。好的。请发布它:) 我认为我们很困惑。 Microsoft.Samples.Security.SSPI.dll 是一个托管的 .Net DLL,用 C++/CLI 编写。您可以从示例下载中获取二进制文件。我正在用我打算发布的 C# 编写我自己的那个 DLL 版本,但你可能只是按原样使用 Microsoft.Samples.Security.SSPI.dll。 你见过 .NET 中的NegotiateStream 吗?【参考方案3】:

我在使用 WindowsIdentity 时完全出错了(这对授权很有好处),因为我忘记了 WCF 处理了很多与配置文件、端点安全和消息/传输安全有关的事情。

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

【讨论】:

【参考方案4】:

曾经尝试过使用 WindowsIdentity 吗? 纯C#/.Net,可序列化,GetCurrent()返回执行账号。


安全令牌 用户向您的应用程序提供一组声明 顺着她的要求背着。在 Web 服务中,这些声明是 在 SOAP 信封的安全标头中携带。在一个 基于浏览器的 Web 应用程序,声明通过 HTTP POST 从 用户的浏览器,以后可能会在会话中缓存在 cookie 中 是需要的。不管它们如何到达,它们必须被序列化 不知何故,这就是安全令牌的用武之地。安全令牌 是一组序列化的声明,由发行方进行数字签名 权威。签名很重要——它让您确信 用户不只是编造一堆索赔并将它们发送给您。 在不需要加密的低安全情况下或 需要,您可以使用未签名的令牌,但这不是我的场景 本文将重点介绍。 WIF 的核心功能之一是 创建和读取安全令牌的能力。 WIF 和底层证券 .NET Framework 中的管道处理所有繁重的加密 解除,并向您的应用程序提供一组声明,您 可以阅读。

引自Windows Identity Foudation WhitePaper

您的主要问题是:

“是否有纯 C#/.NET 方法来使用用户的登录凭据对用户进行身份验证?”

WindowsIdentity“是”您的域控制器颁发的身份验证令牌,在我看来是目前最好的方法。


当我第一次发帖时,我对 WindowsIdentity 知之甚少,但我也觉得它有助于解决您的问题和限制。我做了很多阅读,终于来到了这个page。 WIF 的引入不言自明,WindowsIdentity 是一套新的 .NET 框架,专为基于 Windows/基于角色的安全问题而设计。 SSPI、Kerberos 是整个 Windows 身份验证过程的一部分,用户/机器/进程获得的登录令牌是由域控制器授予的,不能通过“简单”实例化一个新的 WindowsIdentity 对象来获得。如果存在这种非法实例化,整个 Windows 安全模型(域、UAC 等)就会死掉。

这是一个(非常!)小控制台程序,如果您不属于“BUILTIN\Administrateurs”(根据您自己的需要更改组名),则会引发异常。每当“以管理员身份运行”时,程序都会顺利完成。 有非常多的权限集,每个需求都是基于声明的(是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------------\n2"
                    , sec.GetType()
                    , sec.Message
                    , sec.StackTrace));
            
            catch (Exception ex)
            
                Console.WriteLine("This shall not appen.");
            
            Console.WriteLine("Press enter to quit.");
            Console.ReadLine();
        
    

我希望这会有所帮助。

【讨论】:

您能提供更多细节吗?我如何使用它来说服在远程机器上运行的服务我就是我所说的那个人?我看到 WindowsIdentity 是可序列化的,但这无济于事,因为良好的安全设计 (kerberos) 和 SSPI 要求传输的令牌设计为只有接收服务才能使用它们(相互身份验证)。我看不出该类或相关类中的任何一个对此有何帮助,但话说回来,我对那一半 API 还是很陌生。 WindowsIdentity (GetCurrent()) 对象是用户登录时获取的标识令牌。在服务器端使用模拟将允许打开 SQL Server 连接,并将 Integrated_Security 设置为 true(如果授予域用户),即使服务器工作进程帐户不能。对于其他类型的资源(I/O 权限...),它应该都一样。 你需要仔细阅读我原来的问题。 “Kerberos - 设计认证系统:四个场景中的对话” - web.mit.edu/kerberos/dialogue.html SSPI - 从这里阅读简介和身份验证部分:msdn.microsoft.com/en-us/library/ms973911.aspx【参考方案5】:

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

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

【讨论】:

我想使用 .NET 方式(用于自己的客户端/服务器)。 WCF 似乎可以使用它的 netTcpBinding 来做到这一点,但是代码很乱,很难理解。 我不确定您所说的“.NET 方式”是什么意思 - WCF 是 .NET 功能。我确实理解 WCF 学习曲线可能令人生畏,但根据我自己的经验,学习如何使用现有框架比尝试实现自己的框架更好(在开发时间和维护开销方面)。 这个问题很具体。我不想使用 WCF。我希望能够在自己的客户端/服务器中使用 Windows 身份验证。 WCF 可以在它的 netTcpBinding 中做到这一点,所以它是可能的。如果可能的话,我想知道如何在没有 p/invokes 的情况下使用 .NET。它适用于我自己的客户端/服务器库blog.gauffin.org/2014/05/…。我再说一遍:任何暗示 WCF 的答案我都不行。 我明白了。祝你好运!

以上是关于客户端-服务器身份验证 - 使用 SSPI?的主要内容,如果未能解决你的问题,请参考以下文章

除非客户端和服务器使用相同的 Windows 身份,否则带有 sspi 的 WCF Net.tcp 会失败

来自 SSPI 的 KRB_AP_REQ 票证

HTTP 请求未经客户端身份验证方案“协商”的授权。从服务器收到的身份验证标头是“NTLM”

Django Rest 框架中的基于会话的与令牌身份验证

无需手动输入凭据即可进行 SSO 身份验证

用户的表单身份验证和数据库的 Windows?