asp.net - ASP.NET网站+ Windows窗体应用程序+ WCF服务:客户端凭据

标签 asp.net wcf authentication architecture

假设我正在考虑设计WCF服务,其主要目的是提供可被三个不同的应用程序使用的广泛服务:面向公众的网站,内部Windows Forms应用程序和无线移动设备。服务的目的是双重的:(1)在中央位置合并与业务流程相关的代码,(2)最终将对旧数据库的访问锁定并最终一劳永逸地隐藏在一套服务中。

当前,三个应用程序中的每一个都有其自己的持久性和域层,并且对同一数据库的视图略有不同。它们将与WCF服务进行通信,而不是与所有三个应用程序都在与数据库进行通信,而是与某些客户端启用新功能(显然,移动选择器当前无法触发进程发送电子邮件)并集中化通知系统(而不是WCF)。预定的任务每五分钟轮询一次数据库以查找新订单,仅当其中一个客户端调用AcceptNewOrder()服务方法时对ping分页系统进行ping操作。总而言之,到目前为止听起来还不错。

但是,在总体设计方面,我对安全性感到很沮丧。 Windows窗体应用程序当前仅使用Windows主体。员工存储在Active Directory中,并且在应用程序启动时,他们可以以当前Windows用户身份登录(在这种情况下,无需密码),或者可以提供其域名和密码。移动客户端没有任何用户概念。它与数据库的连接是一个硬编码的字符串。该网站在旧版数据库中存储了数千个用户。那么,如何实现身份模型并配置WCF端点来处理呢?

对于Windows Forms应用程序来说,这不是什么大问题:WCF代理可以启动一次并且可以在内存中徘徊,因此我只需要一次客户端凭据(如果代理发生故障,可以再次提示他们)。移动客户端可能只是特殊情况,并使用X509证书针对WCF服务进行身份验证。但是,我该如何处理该网站?

以网站为例,允许匿名访问某些服务。对于需要假想的“客户”角色进行身份验证的服务,我显然不想在每个请求中都对它们进行身份验证,原因有两个:


我每次都需要他们的用户名和密码。将这对信息几乎存储在任何地方(会话,加密的cookie,月球)似乎是个坏主意。
对于每个请求,我都必须点击数据库中的用户表。哎哟。


我唯一能想到的解决方案是将网站视为一个受信任的子系统。 WCF服务需要Web站点提供特定的X509证书。该网站内部使用“表单身份验证”(该服务在返回布尔结果的服务上调用AuthenticateCustomer()方法),可以在凭据列表中添加其他声明,例如“ joe@example.com作为顾客。”然后以某种方式可以在具有该声明的服务上构造自定义IIdentity对象和IPrincipal,WCF服务确信该网站已正确验证了客户的身份(它至少知道,该声明未被篡改,因为它会提前知道该网站的证书)。

完成所有这些操作后,WCF服务代码将能够说出[PrincipalPermission.Demand(Role=MyRoles.Customer)][PrincipalPermission.Demand(Role=MyRoles.Manager)]之类的内容,并且Thread.CurrentPrincipal将具有代表用户的内容(客户的电子邮件地址或专有地址)。员工的姓名,这两个都可用于日志记录和审核)。

换句话说,每种服务将存在两个不同的端点:一个端点接受众所周知的客户端X509证书(用于移动设备和网站),另一个端点接受Windows用户(对于员工)。

对不起,这么久。所以问题是:这有道理吗?提议的解决方案有意义吗?我是否使这个变得太复杂了?

最佳答案

好吧,我想现在我已经花了几个小时来尝试各种方法,所以我会回答自己的问题。

我的第一种方法是在WCF服务和面向公众的网站(该网站是该服务的使用者/客户端)之间建立基于证书的身份验证。用makecert生成的一些测试证书,将它们放入PersonalTrusted PeopleTrusted Root Certification Authorities(因为我不愿意为我们域的证书服务生成真实的证书),对配置文件进行了一些修改,太好了,我们都准备好了。

为了防止该网站必须维护用户的用户名和密码信息,该想法是,一旦用户通过表单身份验证登录到该网站,该网站就可以仅将用户名(可通过HttpContext.Current.User.Identity.Name访问)传递给用户。除了实际上用于保护消息的UserNameSecurityToken之外的可选X509CertificateSecurityToken。如果找到了可选的用户名安全令牌,则WCF服务会说“嘿,这个受信任的子系统说该用户已经正确认证,所以让我为该用户设置一个MyCustomPrincipal并将其安装在当前线程上,以便实际的服务代码可以对此进行检查。”如果不是,那么将安装MyCustomPrincipal的匿名版本。

因此,我花了五个小时试图实现这一目标,并且在various blogs的帮助下,我能够做到这一点。 (我花了大部分时间来调试一个问题,使每个配置和支持类都正确无误,然后在启动主机后而不是之前安装了自定义授权,所以我的努力​​实际上没有生效。有时候我讨厌我有一个TrustedSubsystemAuthorizationPolicy执行X509证书验证,安装了一个匿名MyCustomPrincipal,一个TrustedSubsystemImpersonationAuthorizationPolicy接受了带有空白密码的用户名令牌,并且安装了客户角色MyCustomPrincipal(如果看到)。匿名受信任子系统主体已经安装,并且UserNameAuthorizationPolicy对未使用X509证书的其他端点进行基于常规用户名和密码的验证。它奏效了,很棒。

但。

当我摆弄网站将用来与该服务进行对话的生成的客户端代理代码时,就发生了刺眼的事件。在生成的UserName对象的ClientCredentials属性上指定ClientBase<T>很容易。但是主要的问题是凭据是特定于ChannelFactory的,而不是特定的方法调用。

您会发现,建立WCF客户端代理的new()比more expensive you might think。我编写了一个快速实用的应用程序来测试自己的性能:new()建立一个新代理并调用十次方法都花费了大约6秒钟,而new()建立一个代理并仅调用该方法十倍的成本大约一秒的3/5分。那只是一个令人沮丧的性能差异。

所以我可以为客户端代理实现一个池或缓存,对吗?嗯,不,它不容易解决:客户端凭据信息处于渠道工厂级别,因为它可能被用于保护传输,而不仅仅是消息,并且某些绑定使实际的传输在服务调用之间保持开放。由于客户端凭据对于代理对象是唯一的,因此这意味着我必须为网站上当前的每个用户拥有一个唯一的缓存实例。内存中可能有很多代理对象,而@#$ @#很接近我一开始试图避免的问题!而且,由于无论如何我都必须触摸Endpoint属性以设置可选的支持用户名令牌的绑定,因此我无法利用Microsoft在.NET 3.5中“免费”添加的automatic channel factory caching

回到图板:我的第二种方法,也是我认为现在将要使用的一种方法,是坚持客户端网站和WCF服务之间的X509证书安全性。我将在消息中发送一个自定义的“ UserName” SOAP头,WCF服务可以检查该SOAP头,确定它是否来自可信的子系统(例如网站),如果是,则在其中安装MyCustomPrincipal与以前类似的方式。

Codeprojectrandom 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和用于审核目的的日志记录现在很容易。

我希望这会在将来帮助一些可怜的人。

关于asp.net - ASP.NET网站+ Windows窗体应用程序+ WCF服务:客户端凭据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/345414/

相关文章:

php - 验证客户端 JavaScript 事件的最佳方法

c# - TextBox - asp.net 上是否有进入和离开事件?

asp.net - 在 asp.net 中显示多个状态消息

c# - 与 Web 角色进行内部 tcp 连接后,Azure 辅助角色变得无响应

authentication - 如何根据已知地址验证电话号码?

web-services - 每次调用 Web 服务时重复用户名/密码身份验证

c# - C#中如何获取当前页面的URL

c# - 必须定义参数 '@email'

c# - 使用 .Net Standard 2.0 库中的 x509 证书使用 WCF Web 服务?

c# - 安装 .NET Framework 4.5 后 HttpContext.Current.User 为空