首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >ASP.NET网站+ Windows Forms应用程序+ WCF服务:客户端凭据

ASP.NET网站+ Windows Forms应用程序+ WCF服务:客户端凭据
EN

Stack Overflow用户
提问于 2008-12-05 22:39:20
回答 2查看 10.7K关注 0票数 18

假设我正在考虑设计一个WCF服务,其主要目的是提供可供三个完全不同的应用程序使用的广泛服务:面向公众的Web站点、内部Windows窗体应用程序和无线移动设备。该服务的目的有两个:(1)将与业务流程相关的代码合并到一个中心位置;(2)锁定对遗留数据库的访问,最终一劳永逸地将其隐藏在一套服务之后。

目前,这三个应用程序中的每一个都有自己的持久层和域层,它们对同一数据库的视图略有不同。它们不是所有三个应用程序都与数据库对话,而是与WCF服务对话,启用来自某些客户端的新功能(显然,移动选择器当前不能触发进程发送电子邮件),并集中通知系统(而不是每五分钟轮询数据库以获取新订单的计划任务,只需在其中一个客户端调用AcceptNewOrder()服务方法时ping开销分页系统)。总而言之,到目前为止,这听起来相当合理。

然而,在总体设计方面,当谈到安全性时,我被难住了。Windows窗体应用程序当前只使用Windows主体;员工存储在Active Directory中,在应用程序启动时,他们可以作为当前Windows用户登录(在这种情况下不需要密码),也可以提供他们的域名和密码。移动客户端没有任何用户的概念;它与数据库的连接是硬编码的字符串。并且该网站有数千名用户存储在遗留数据库中。那么,如何实现身份模型并配置WCF端点来处理此问题呢?

就Windows窗体应用程序而言,这不是什么大问题: WCF代理可以启动一次,并且可以在内存中挂起,因此我只需要客户端凭据一次(如果代理出现故障,可以再次提示输入它们)。移动客户端可以是特殊情况,并使用X509证书进行身份验证。但是我该怎么处理这个网站呢?

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

  • 我每次都需要他们的用户名和密码。将这两个信息存储在几乎任何地方--会话、加密的cookie、月球--似乎不是一个好主意。
  • 对于每个请求,我都必须访问数据库中的users表。哦,

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

所有这些都准备就绪后,WCF服务代码将能够表示[PrincipalPermission.Demand(Role=MyRoles.Customer)][PrincipalPermission.Demand(Role=MyRoles.Manager)]之类的内容,并且Thread.CurrentPrincipal将具有表示用户的内容(客户的电子邮件地址或员工的可分辨名称,这两个名称对于日志记录和审计都很有用)。

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

抱歉,这太长了。所以问题是:这一切有意义吗?建议的解决方案有意义吗?我是不是把事情搞得太复杂了?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2008-12-09 01:52:43

好吧,既然我已经花了几个小时尝试各种方法,我想我将尝试一下我自己的问题。

我的第一种方法是在WCF服务和面向公众的Web站点( Web站点是服务的使用者/客户端)之间设置基于证书的身份验证。使用makecert生成一些测试证书,将它们放到PersonalTrusted PeopleTrusted Root Certification Authorities中(因为我不想费心针对我们域的证书服务生成真正的证书),一些配置文件修改,很好,我们都准备好了。

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

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

但。

当我摆弄Web站点将用来与此服务对话的生成的客户端代理代码时,出现了刺伤自己眼睛的时刻。在生成的ClientBase<T>对象的ClientCredentials属性上指定UserName非常简单。但主要问题是凭据是特定于ChannelFactory的,而不是特定的方法调用。

你看,more expensive () up a WCF client proxy比you might think更简单。我自己编写了一个快捷的应用程序来测试性能: new ()设置一个新的代理并调用一个方法10次大约需要6秒,而new()设置一次代理并只调用该方法10次大约需要1/5秒。这只是一个令人沮丧的性能差异。

因此,我可以只为客户端代理实现一个池或缓存,对吗?不,这并不容易解决:客户端凭据信息处于通道工厂级别,因为它可能用于保护传输,而不仅仅是消息,而且一些绑定会在服务调用之间保持实际传输的打开。由于客户端凭据对于代理对象是唯一的,这意味着我必须为当前Web站点上的每个用户提供一个唯一的缓存实例。这可能是大量的代理对象驻留在内存中,并且与我一开始试图避免的问题非常接近( @#$@# )!而且由于我无论如何都要接触Endpoint属性来为可选的支持用户名令牌设置绑定,所以我不能利用微软在.NET 3.5中“免费”添加的automatic channel factory caching

回到起点:我的第二种方法,也是我认为现在将最终使用的一种方法,是坚持使用客户端网站和X509服务之间的WCF证书安全性。我只需在我的消息中发送一个自定义的"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服务中的实际服务代码可以执行以下操作

代码语言:javascript
复制
    // 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和用于审计目的的日志记录现在变得轻而易举。

我希望这能在未来帮助一些可怜的灵魂。

票数 15
EN

Stack Overflow用户

发布于 2009-04-02 18:14:39

对于匿名公共访问,使用基本的basichttpbinding,并在web.config文件中包含以下内容

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/345414

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档