当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《ASP.NET网站+Windows窗体应用程序+WCF服务:客户端凭据》经验,为你挑选了1个好方法。

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

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

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

就Windows窗体应用程序而言,这不是一个大问题:WCF代理可以启动一次并且可以在内存中闲逛,所以我只需要一次客户端凭据(如果代理有问题,可以再次提示它们).移动客户端可以是特殊的,并使用X509证书对WCF服务进行身份验证.但是我该如何处理这个网站呢?

在网站的情况下,允许匿名访问某些服务.对于需要在假设的"客户"角色中进行身份验证的服务,我显然不希望在每个请求上对它们进行身份验证,原因有两个:

我每次都需要他们的用户名和密码.几乎可以在任何地方存储这对信息 - 会话,加密的cookie,月亮 - 似乎是一个坏主意.

我必须为每个请求点击数据库中的users表.哎哟.

我能想到的唯一解决方案是将Web站点视为可信子系统.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用户(用于员工).

对不起,这么久.所以问题是:这有什么意义吗?提议的解决方案是否有意义?我是不是太复杂了?



1> Nicholas Pia..:

好吧,我估计现在我已经花了几个小时玩各种方法,我会抓住自己的问题.

我的第一种方法是在WCF服务和面向公众的Web站点之间设置基于证书的身份验证(Web站点是服务的使用者/客户端).与产生的一些测试证书makecert,扑通他们进入Personal,Trusted People以及Trusted Root Certification Authorities(因为我不能打扰产生对我们的域名证书服务以假乱真),一些配置文件修改,和伟大的,我们所有的设置.

为了防止Web站点必须维护的用户名和密码信息对于用户来说,这个想法是,一旦用户登录到通过窗体身份验证的网站,该网站可以通过只是用户名(通过访问HttpContext.Current.User.Identity.Name)作为可选UserNameSecurityToken除了在X509CertificateSecurityToken实际使用,以确保该消息.如果找到可选的用户名安全令牌,那么WCF服务会说"嘿,这个受信任的子系统说该用户已经过正确的身份验证,所以让我MyCustomPrincipal为该用户设置一个并将其安装在当前线程上,以便实际的服务代码可以检查一下." 如果不是,那么MyCustomPrincipal将安装匿名版本.

所以,我花了五个小时试图实现这一点,并在各种 博客的帮助下,我能够做到这一点.(我花了大部分时间调试一个问题,我在每个配置和支持类都正确,然后我启动主机安装我的自定义授权,而不是之前,所以我的努力​​没有真正生效.有些日子我讨厌计算机.)我有一个TrustedSubsystemAuthorizationPolicyX509证书验证,安装匿名MyCustomPrincipal,一个TrustedSubsystemImpersonationAuthorizationPolicy用空密码接受用户名令牌,并且MyCustomPrincipal如果它看到已经安装了匿名可信子系统主体,则安装了一个客户角色,UserNameAuthorizationPolicy它为没有使用X509证书的其他端点进行了常规的基于用户名和密码的验证.它奏效了,很精彩.

但.

当我摆弄网站用来与这项服务交谈的生成的客户端代理代码时,就会出现刺入眼球的时刻.指定生成对象UserNameClientCredentials属性ClientBase很容易.但主要问题是凭证特定于a ChannelFactory,而不是特定的方法调用.

你看,新的()WCF客户端代理比你想象的要贵.我写了一个快速而又脏的应用程序来自己测试性能:new()新代理和调用方法十次需要大约6秒而new()使用代理一次并仅调用方法10次成本约一秒钟的3/5秒.这只是一个令人沮丧的性能差异.

所以我可以为客户端代理实现一个池或缓存,对吧?嗯,不,它不容易解决:客户端凭据信息在通道工厂级别,因为它可能用于保护传输,而不仅仅是消息,并且一些绑定在服务调用之间保持实际传输打开.由于客户端凭据对于代理对象是唯一的,这意味着我必须为当前网站上的每个用户提供唯一的缓存实例.这可能是很多代理对象都存在于内存中,并且非常漂亮@#$ @#接近我试图首先避免的问题!因为Endpoint无论如何我必须触摸属性以设置可选支持用户名令牌的绑定,我无法利用自动通道工厂缓存 微软在.NET 3.5中添加了"免费".

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

Google上的Codeproject和随机人员都是很棒的事情,因为他们帮助我快速启动并运行,即使在配置中的自定义端点行为遇到奇怪的WCF错误之后也是如此.通过在客户端和服务端实现消息检查器 - 一个用于添加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服务器中,或者有些人生活在Active Directory中:PrincipalPermission.Demand为审计目的进行日志记录现在非常简单.

我希望这有助于将来一些可怜的灵魂.

推荐阅读
拾味湖
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有