目前,三个应用程序中的每一个都有自己的持久性和域层,同一数据库的视图略有不同。而不是所有三个应用程序与数据库通信,他们会与WCF服务通话,从而允许一些客户端的新功能(移动选择程序当前无法触发进程发送电子邮件,显然)和集中通知系统(而不是一个计划任务轮询数据库每五分钟一次新订单,只要ping这个开销分页系统,当AcceptNewOrder()服务方法由其中一个客户端调用)。总而言之,这听起来非常理智。
然而,在总体设计方面,在安全性方面,我很失望。 Windows窗体应用程序目前只使用Windows主体;员工存储在Active Directory中,并且在应用程序启动时,他们可以以当前Windows用户身份登录(在这种情况下不需要密码),或者可以提供其域名和密码。移动客户端没有用户的任何概念;它与数据库的连接是一个硬编码的字符串。而网站上有数千个用户存储在遗留数据库中。那么如何实现身份模型并配置WCF端点来处理这个问题?
在Windows Forms应用程序方面,这不是一个很大的问题:WCF代理可以启动一次,并且可以在内存中挂起,所以我只需要一次客户端凭证(如果代理有故障,可以再次提示它们)。移动客户端可以是特殊的套件,并使用X509证书来验证WCF服务。但是我该怎么做网站呢?
在网站的情况下,允许匿名访问某些服务。而对于在假设的“客户”角色中需要身份验证的服务,我显然不希望在每个请求上进行身份验证,原因如下:
>每次我需要他们的用户名和密码。在任何地方存储这对信息 – 会话,一个加密的cookie,月亮 – 似乎是一个坏主意。
>我将不得不针对每个请求打入数据库中的users表。哎哟。
我可以想出的唯一解决方案是将网站视为受信任的子系统。 WCF服务期望来自网站的特定X509证书。该网站在内部使用Forms Authentication(在返回布尔结果的服务上调用AuthenticateCustomer()方法)可以向凭据列表添加额外的声明,例如“joe@example.com以顾客。”那么在某种程度上,一个自定义的IIdentity对象和IPrincipal可以在该索赔的服务上构建,WCF服务确信网站已经对客户进行了正确的身份验证(它将知道索赔没有被篡改,至少因为它将提前了解网站的证书)。
有了所有这些,WCF服务代码将能够说出[PrincipalPermission.Demand(Role = MyRoles.Customer)]或[PrincipalPermission.Demand(Role = MyRoles.Manager)],Thread.CurrentPrincipal将具有代表用户的东西(客户的电子邮件地址或员工的可分辨名称,两者都对日志记录和审核有用)。
换句话说,每个服务将存在两个不同的端点:一个接受了众所周知的客户端X509证书(用于移动设备和网站)以及接受Windows用户(对于员工)的端点。
抱歉这是这么久。所以问题是:这是否有意义?提出的解决方案是否有意义?我是否使这太复杂了?
解决方法
我的第一种方法是在WCF服务和面向公众的网站之间建立基于证书的身份验证(网站是服务的消费者/客户端)。使用makecert生成的几个测试证书,将其引入个人,受信任的人员和受信任的根证书颁发机构(因为我无法为我们的域的证书服务生成真实的证书),一些配置文件修改,伟大的,我们全部设定
为了防止网站对用户维护用户名和密码信息,这个想法是,一旦用户通过表单身份验证登录到网站,该网站就可以传递用户名(可以通过HttpContext.Current.User访问)。 Identity.Name)作为可选的UserNameSecurityToken以及实际用于保护邮件的X509CertificateSecurityToken。如果找到可选的用户名安全令牌,那么WCF服务会说“嘿,这个信任的子系统说这个用户被正确认证,所以让我设置一个MyCustomPrincipal为该用户安装在当前的线程上,以便实际的服务代码可以检查这个。“如果没有,那么将安装一个匿名版本的MyCustomPrincipal。
所以我去了五个小时试图实现这个,并在various blogs的帮助下,我能够做到这一点。 (我花了大部分时间来调试一个问题,我每个配置和支持类都是正确的,然后在我启动主机之后安装了我的自定义授权,而不是之前,所以我的努力实际上没有效果。有些日子我讨厌计算机)。我有一个TrustedSubsystemAuthorizationPolicy进行了X509证书验证,安装了一个匿名MyCustomPrincipal,一个TrustedSubsystemImpersonationAuthorizationPolicy,接受一个空白密码的用户名令牌,并安装了一个客户角色MyCustomPrincipal,如果它看到匿名信任的子系统主体已经安装,以及一个UserNameAuthorizationPolicy,它对未使用X509证书的其他端点进行了常规的用户名和密码验证。它的工作,这是美好的。
但。
当我正在使用网站用于与此服务通话的生成的客户端代理代码时,刺激自己的眼球瞬间来临。在生成的ClientBase< T>的ClientCredentials属性上指定UserName对象很容易。但主要的问题是凭据特定于ChannelFactory,而不是特定的方法调用。
你看到,new()创建一个WCF客户端代理是more expensive比you might think.我写了一个快速而肮脏的应用程序来测试自己的性能:new()创建一个新的代理和调用一个方法十次花了大约6秒而new()启动代理一次,只调用方法10次,花费约1/5秒。这只是令人沮丧的表现差异。
所以我可以为客户端代理实现一个池或缓存,对吧?好的,不,它不容易解决:客户端凭据信息在通道工厂级别,因为它可能用于保护传输,而不仅仅是消息,一些绑定在服务调用之间保持实际的传输打开。由于客户端凭据对代理对象是唯一的,这意味着我必须为网站上的每个用户拥有唯一的缓存实例。这可能是很多代理对象坐在内存中,而且@#$ @#接近于我首先想避免的问题!而且由于我必须触摸Endpoint属性来设置可选支持用户名令牌的绑定,所以我无法利用Microsoft在.NET 3.5中添加“免费”的automatic channel factory caching。
回到绘图板:我的第二个方法,而我认为我现在最终会使用的是在客户端网站和WCF服务之间坚持X509证书的安全性。我将在我的消息中发送一个自定义的“UserName”SOAP头,WCF服务可以检查SOAP头,确定它是否来自受信任的子系统(如Web站点),如果是这样,请在类似的情况下安装MyCustomPrincipal如前所述。
Codeproject和random people on Google是美妙的事情,因为他们帮助我快速运行,即使在running into a weird WCF bug when it comes to custom endpoint behaviors in configuration之后。通过在客户端和服务端实现消息检查员 – 一个添加UserName头,一个读取它,安装正确的校长 – 这个代码在一个地方,我可以简单的忘掉它。由于我不必触摸端点属性,所以我可以免费获得内置的通道工厂缓存。而且由于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}",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和用于审计的日志记录现在已经很快了。
我希望这将有助于一些不好的灵魂。