好吧,既然我已经花了几个小时尝试各种方法,我想我会尝试自己的问题。
我的第一个方法是在 WCF 服务和面向公众的网站之间设置基于证书的身份验证(网站是服务的消费者/客户端)。使用makecert 生成的一些测试证书,将它们放入Personal、Trusted People 和Trusted Root Certification Authorities(因为我懒得针对我们域的证书服务生成真实证书),一些配置文件修改,太好了,我们都准备好了。
为了防止网站必须为用户维护用户名和密码信息,其想法是一旦用户通过表单身份验证登录到网站,网站就可以只传递用户名(可通过HttpContext.Current.User.Identity.Name 访问) 作为可选的UserNameSecurityToken 以及实际用于保护消息的X509CertificateSecurityToken。如果找到可选的用户名安全令牌,那么 WCF 服务会说“嘿,这个受信任的子系统说这个用户已经过正确的身份验证,所以让我为那个用户设置一个 MyCustomPrincipal 并将它安装在当前线程上,这样实际的服务代码可以检查这一点。”如果不是,则将安装 MyCustomPrincipal 的匿名版本。
所以我花了五个小时尝试实现这一点,在variousblogs 的帮助下,我能够做到。 (我大部分时间都在调试一个问题,我的每个配置和支持类都正确,然后在我启动主机之后安装了我的自定义授权,而不是之前,所以我的努力实际上都没有生效。有些日子我讨厌电脑。)我有一个 TrustedSubsystemAuthorizationPolicy 进行 X509 证书验证,安装了一个匿名 MyCustomPrincipal,一个 TrustedSubsystemImpersonationAuthorizationPolicy 接受了一个带有空白密码的用户名令牌并安装了一个客户角色@ 987654342@ 如果它看到匿名受信子系统主体已安装,UserNameAuthorizationPolicy 则对未使用 X509 证书的其他端点进行基于用户名和密码的常规验证。它奏效了,而且很棒。
但是。
当我摆弄生成的客户端代理代码时,网站将使用该代码与该服务通信,这让我目瞪口呆的时刻到来了。在生成的ClientBase<T> 对象的ClientCredentials 属性上指定UserName 非常简单。但主要问题是凭据特定于 ChannelFactory,而不是特定的方法调用。
你看,新建一个 WCF 客户端代理是 more expensive 而不是 you might think。我自己编写了一个快速而肮脏的应用程序来测试性能:new() 建立一个新代理并调用一个方法十次大约需要 6 秒,而 new() 建立一个代理并仅调用该方法 10 次成本大约 3/5 秒。这只是令人沮丧的性能差异。
所以我可以为客户端代理实现一个池或缓存,对吗?好吧,不,它不容易解决:客户端凭据信息处于通道工厂级别,因为它可能用于保护传输,而不仅仅是消息,并且某些绑定在服务调用之间保持实际传输打开。由于客户端凭据对于代理对象是唯一的,这意味着我必须为当前在 Web 站点上的每个用户拥有一个唯一的缓存实例。这可能是很多代理对象位于内存中,并且非常接近我一开始试图避免的问题!而且由于无论如何我都必须触摸Endpoint 属性来设置可选支持用户名令牌的绑定,因此我无法利用Microsoft 在.NET 3.5 中“免费”添加的automatic channel factory caching。
回到绘图板:我的第二种方法,也是我认为我现在将最终使用的方法,是坚持客户端网站和 WCF 服务之间的 X509 证书安全性。我将在我的消息中发送一个自定义的“UserName”SOAP 标头,WCF 服务可以检查该 SOAP 标头,确定它是否来自受信任的子系统(例如网站),如果是,则在其中安装 MyCustomPrincipal方式和以前类似。
Codeproject 和 random people on Google 是很棒的东西,因为它们帮助我快速启动并运行它,即使在 running into a weird WCF bug when it comes to custom endpoint behaviors in configuration 之后也是如此。通过在客户端和服务端实现消息检查器——一个用于添加 UserName 标头,一个用于读取它并安装正确的主体——此代码位于一个我可以简单地忘记它的地方。由于我不必触摸Endpoint 属性,因此我免费获得了内置通道工厂缓存。由于ClientCredentials 对于访问网站的任何用户都是相同的(实际上,它们始终是 X509 证书——只有消息本身中的 UserName 标头的值会发生变化),因此添加客户端代理缓存或代理池是更琐碎。
所以这就是我最终做的。 WCF 服务中的实际服务代码可以做类似的事情
// Scenario 1: X509Cert + custom UserName header yields for a Web site customer ...
Console.WriteLine("{0}", Thread.CurrentPrincipal.Identity.Name); // prints out, say, "joe@example.com"
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Customer)); // prints out "True"
// Scenario 2: My custom UserNameSecurityToken authentication yields for an employee ...
Console.WriteLine("{0}", Thread.CurrentPrincipal.Identity.Name); // prints out, say, CN=Nick,DC=example, DC=com
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Employee)); // prints out "True"
// Scenario 3: Web site doesn't pass in a UserName header ...
Console.WriteLine("{0}", Thread.CurrentPrincipal.Identity.Name); // prints out nothing
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Guest)); // prints out "True"
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Customer)); // prints out "False"
无论这些人是如何通过身份验证的,或者有些人使用 SQL Server 或有些人使用 Active Directory:PrincipalPermission.Demand 并且用于审计目的的日志记录现在变得轻而易举。
我希望这对将来的一些可怜的人有所帮助。