使用 C# 和 Kerberos 进行 Web 服务身份验证 (POC)
这是一篇概念验证(POC)文章,旨在解释如何在用户需要请求 Web 服务时实现 Kerberos 身份验证来验证用户。
引言
本文是一篇概念验证(POC)文章。它解释了如何使用 WSE 3.0 实现 Kerberos 身份验证来验证用户请求 Web 服务时的身份。这个目标非常重要,尤其是在使用面向服务架构(SOA)时。在阅读了 Microsoft 的一项模式与实践:《Web 服务安全指南》后,我决定撰写这篇文章,这是一份关于此问题的非常有价值的参考资料。
下面的文章不会详细描述 Kerberos 身份验证的工作原理,并假设读者对此有一定的了解。它侧重于如何使用 Microsoft 技术来实现它。单点登录(SSO)实现是此概念的主要用途。例如:假设我们有两个域用户,一个市场营销组用户和一个会计组用户。他们每个人都可以登录到自己的 Windows 账户,然后打开公司网站,而无需针对公司网站进行身份验证。登录只需要在登录 Windows 时进行一次。当然,公司网站只会为每个用户提供其组权限。简单来说,我们的系统中将有以下参与者:
- Web 服务:负责验证任何需要请求它的客户端。
- 客户端:需要请求 Web 服务;在请求 Web 服务时应提供身份验证凭据。
- Kerberos 分发中心(KDC):负责验证客户端并 issuing 一个包含客户端凭据的票据,然后客户端可以使用它来与 Web 服务进行身份验证。对于 Microsoft 环境,KDC 在 Windows Server 2003 中可用,例如,它是一个域控制器。
上图显示了我们系统参与者之间的关系。现在,我们将文章分为两部分:
- 第一部分:实现演示所需的基础知识。它可以用以下几点来说明:
- Kerberos 身份验证过程概述。
- 准备 Web 服务和 IIS 配置。
- 在 Web 服务上应用 Kerberos 身份验证。
- 在客户端应用程序上应用 Kerberos 身份验证。
- 第二部分:根据第一部分描述一个非常简单的演示。
第一部分
Kerberos 身份验证过程概述
简而言之,当客户端需要请求服务时,它会执行五个步骤,如下图所示:
我想更详细地解释步骤 2 和 4。
- 在步骤 2 中,KDC 执行以下操作:
- 生成一个新的会话密钥。
- 将新生成的会话密钥和一些客户端数据打包到服务票据中。
- 使用 Web 服务的母密钥(Web 服务知道)加密服务票据。
- 将加密的服务票据和新的会话密钥发送给客户端。
然后,客户端执行以下操作:
- 使用会话密钥加密验证器(包含时间戳和其他信息)。
- 将加密的验证器和服务票据打包到新的 Kerberos 安全令牌中。
- 在步骤 4 中,Web 服务执行以下操作:
- 从 Kerberos 令牌中获取服务票据。
- 使用其母密钥解密它。
- 从解密的服务票据中获取会话密钥。
- 使用会话密钥解密验证器并验证文本。
准备 Web 服务和 IIS 配置
在本节中,我们将更详细地了解母密钥以及如何拥有一个具有唯一母密钥的 Web 服务。什么是用于加密服务票据的母密钥?在 Windows 服务器中:KDC 上注册的每个对象(计算机或用户)都有一个共享密钥(也称为母密钥)。此共享密钥用于加密服务票据,也用于解密它。该对象通过一个唯一的名称 SPN(服务主体名称)在 KDC 上注册,因此在向 KDC 请求票据时,应确定 SPN。默认情况下,Windows 上运行的所有服务都使用内置的 Network Service 账户,并且引用它的默认 SPN 是“host/PCName”。IIS 的默认应用程序池使用 Network Service 账户向 Windows 标识自己。这意味着,当为 IIS 上的 Web 服务生成服务票据时,它将使用与“Network Service”账户相关的共享密钥进行加密,然后使用相同应用程序池的任何 Web 服务都可以解密该票据。
要为 Web 服务分配一个新的母密钥,您需要执行以下操作:
- 向 Active Directory 添加一个新的域账户。
- 创建一个新的 SPN 来引用新的域账户。
- 在 IIS 中添加一个新的应用程序池,并将其标识映射到新的域账户。
- 配置 Web 服务虚拟目录以使用新的应用程序池。
- 授予新账户访问 Web 服务虚拟目录所需的所有权限,并使其能够作为 IIS_WPG 组的成员。
- 重启 IIS。
详细说明
创建新的 SPN 来引用新的域账户
Setspn 工具包含在 Windows Support Tools 中,用于管理 SPN。
在命令提示符下键入以下命令:Setspn –a http/ServiceName DomainName\DomainAccount,其中:
- ServiceName:是 Web 服务的名称。
- DomainName:是域的名称。
- DomainAccount:是新的域账户名。
在 IIS 中添加新的应用程序池,并将其标识映射到新的域账户
配置 Web 服务虚拟目录以使用新的应用程序池
授予新账户访问 Web 服务虚拟目录并作为 IIS_WPG 组的成员所需的所有权限
- 将新域账户添加到 IIS_WPG 组。
- 为该新域账户分配以下两个用户权限以启动 CGI 进程:Adjust memory quotas for a process(调整进程的内存配额)和 Replace a process level token(替换进程级别令牌)。
- 授予新域账户对 Web 服务文件夹的完全控制权限。
- 授予新域账户对 Windows 目录下的 temp 文件夹的完全控制权限。
在 Web 服务上应用 Kerberos 身份验证
要在 Web 服务中使用 Kerberos 身份验证:
- 启用 WSE 3.0,并启用策略。
- 添加策略文件并配置策略。
- 将策略应用于 Web 服务。
详细说明
- 启用 WSE 3.0 并启用策略:通过在 web.config 文件中添加以下标签:
<configSections> <section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections> <system.web> <compilation debug="true"> <assemblies> <add assembly="Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </assemblies> </compilation> <webServices> <soapExtensionImporterTypes> <add type="Microsoft.Web.Services3.Description.WseExtensionImporter, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </soapExtensionImporterTypes> <soapServerProtocolFactory type="Microsoft.Web.Services3.WseProtocolFactory,Microsoft.Web.Services3, Version=3.0.0.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </webServices> </system.web> <microsoft.web.services3> <policy fileName="wse3policyCache.config" /> <tokenIssuer> <statefulSecurityContextToken enabled="false" /> </tokenIssuer> </microsoft.web.services3>
- 添加策略文件并配置策略:向您的项目添加一个名为“wse3policyCache.config”的配置文件,然后添加以下标签:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy"> <policy name="KerberosService"> <authorization> <allow user="Mawhiba\Akram" /> <deny role="*" /> </authorization> <kerberosSecurity establishSecurityContext="true" renewExpiredSecurityContext="true" requireSignatureConfirmation="false" messageProtectionOrder="SignBeforeEncryptAndEncryptSignature" requireDerivedKeys="true" ttlInSeconds="300"> <protection> <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" /> </protection> </kerberosSecurity> <requireActionHeader /> </policy> </policies>
授权部分可以根据业务角色进行更改。
- 将策略应用于 Web 服务:通过在服务类之前添加以下代码:
[Policy("KerberosService")]
在客户端应用程序上应用 Kerberos 身份验证
要在客户端中使用 Kerberos 身份验证:
- 启用 WSE 3.0,并启用策略。
- 添加策略文件并配置策略。
- 使用 Web 服务的增强版本并在客户端应用策略。
详细说明
- 启用 WSE 3.0 并启用策略:通过在 web.config 文件中添加以下标签:
<configSections> <section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections> <system.web> <compilation debug="true"> <assemblies> <add assembly="Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </assemblies> </compilation> <webServices> <soapExtensionImporterTypes> <add type="Microsoft.Web.Services3.Description.WseExtensionImporter, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </soapExtensionImporterTypes> </webServices> </system.web> <microsoft.web.services3> <policy fileName="wse3policyCache.config" /> </microsoft.web.services3>
- 添加策略文件并配置策略:向您的项目添加一个名为“wse3policyCache.config”的配置文件,然后添加以下标签:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy"> <policy name="KerberosClient"> <kerberosSecurity establishSecurityContext="true" renewExpiredSecurityContext="true" requireSignatureConfirmation="false" messageProtectionOrder="SignBeforeEncryptAndEncryptSignature" requireDerivedKeys="true" ttlInSeconds="300"> <token> <kerberos targetPrincipal="http/ServiceName" impersonationLevel="Impersonation" /> </token> <protection> <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" /> </protection> </kerberosSecurity> <requireActionHeader /> </policy> </policies>
其中,“
http/ServiceName
”是所需服务的 SPN。 - 使用 Web 服务的增强版本并在客户端应用策略:使用“Wse”版本,如下面的代码所示:
Client.localhost.ServiceWse myService = new ServiceWse(); myService.SetPolicy("KerberosClient");
第二部分
好的,我们现在可以开始我们的简单项目来演示 Kerberos 身份验证了。
- 创建两个具有不同母密钥的 Web 服务(webservice1 和 webservice2)。
- 应用 Kerberos 身份验证,并为每个 Web 服务添加允许某些用户访问并拒绝其他用户访问的策略。
<authorization> <allow user="CodeProject\Akram" /> <deny role="*" /> </authorization>
- 在两个 Web 服务中,编写一个测试方法,如下所示:
[WebMethod] public string Test() { return "Succeeded "; }
- 创建一个控制台应用程序并添加对这两个 Web 服务的引用。
- 将这两个 Kerberos 身份验证策略都应用到它(一个策略用于请求 webservice1,名为 '
KerberosClient1
';另一个用于请求 webservice2,名为 'KerberosClient2
')。策略文件将是这样的:<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy"> <policy name="KerberosClient1"> <kerberosSecurity establishSecurityContext="true" renewExpiredSecurityContext="true" requireSignatureConfirmation="false" messageProtectionOrder="SignBeforeEncryptAndEncryptSignature" requireDerivedKeys="true" ttlInSeconds="300"> <token> <kerberos targetPrincipal="http/WS1" impersonationLevel="Impersonation" /> </token> <protection> <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" /> </protection> </kerberosSecurity> <requireActionHeader /> </policy> <policy name="KerberosClient2"> <kerberosSecurity establishSecurityContext="true" renewExpiredSecurityContext="true" requireSignatureConfirmation="false" messageProtectionOrder="SignBeforeEncryptAndEncryptSignature" requireDerivedKeys="true" ttlInSeconds="300"> <token> <kerberos targetPrincipal="http/WS2" impersonationLevel="Impersonation" /> </token> <protection> <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" /> </protection> </kerberosSecurity> <requireActionHeader /> </policy> </policies>
这个控制台应用程序没有 web.config 文件,所以我们可以使用 app.config 文件来启用 WSE。修改并添加 Web 引用后,App.config 将如下所示:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3,Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="Client.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </sectionGroup> </configSections> <microsoft.web.services3> <policy fileName="wse3policyCache.config" /> </microsoft.web.services3> <applicationsettings /> <client.properties.settings /> <setting name="Client_localhost_Service" serializeAs="String"> <value />https:///WS_Kerb_Demo/Service.asmx</value> </setting> <setting name="Client_localhost2_Service" serializeAs="String"> <value>https:///WS_Kerb_Demo2/Service.asmx</value> </setting> </Client.Properties.Settings> </applicationSettings> </configuration>
- 编写一些代码,尝试使用这两个策略请求这两个 Web 服务,如下所示:
class Program { static void Main(string[] args) { localhost1.ServiceWse service1 = new localhost1.ServiceWse(); localhost2.ServiceWse service2 = new localhost2.ServiceWse(); service1.SetPolicy("KerberosClient1"); Console.WriteLine(""); Console.WriteLine("(1) Token with first SPN " + "and calling the first service "); try { Console.WriteLine(service1.Test()); } catch { Console.WriteLine("Failed"); } Console.ReadLine(); Console.WriteLine(""); service2.SetPolicy("KerberosClient1"); Console.WriteLine("(2) Token with first SPN " + "and calling the secound service "); try { Console.WriteLine(service2.Test()); } catch { Console.WriteLine("Failed "); } Console.ReadLine(); Console.WriteLine(""); service1.SetPolicy("KerberosClient2"); Console.WriteLine("(3) Token with secound SPN" + " and calling the first service "); try { Console.WriteLine(service1.Test()); } catch { Console.WriteLine("Failed "); } Console.ReadLine(); Console.WriteLine(""); service2.SetPolicy("KerberosClient2"); Console.WriteLine("(4) Token with secound SPN" + " and calling the secound service "); try { Console.WriteLine(service2.Test()); } catch { Console.WriteLine("Failed "); } Console.ReadLine(); } }
- 运行应用程序,您将看到以下输出:
谢谢
我希望这篇文章对某些人有所帮助。这是我在 The Code Project 上的第一篇文章,希望不是最后一篇。最后,我想感谢位于沙特阿拉伯利雅得的 Arabian Advanced Systems (AAS),我从中获得了文章的大部分知识,感谢他们的支持和团队合作。