代码之家  ›  专栏  ›  技术社区  ›  Nicholas Piasecki

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

  •  18
  • Nicholas Piasecki  · 技术社区  · 17 年前

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

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

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

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

    • 对于每个请求,我都必须点击数据库中的users表。哎哟

    AuthenticateCustomer() 方法),可以向凭据列表中添加其他声明,如“joe@example.com然后可以在该声明的服务上构建自定义IIdentity对象和IPrincipal,WCF服务确信网站已正确验证了客户身份(至少它会知道索赔未被篡改,因为它会提前知道网站的证书)。

    [PrincipalPermission.Demand(Role=MyRoles.Customer)] 或 [PrincipalPermission.Demand(Role=MyRoles.Manager)] ,以及 Thread.CurrentPrincipal 将具有代表用户的内容(客户的电子邮件地址或员工的可分辨名称,两者都对日志记录和审核有用)。

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

    对不起,时间太长了。所以问题是:这些有意义吗?提议的解决方案有意义吗?我是不是把事情弄得太复杂了?

    2 回复  |  直到 17 年前
        1
  •  15
  •   Nicholas Piasecki    17 年前

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

    makecert Personal , Trusted People 和 Trusted Root Certification Authorities (因为我懒得针对我们域的证书服务生成真正的证书),一些配置文件修改,很好,我们都准备好了。

    HttpContext.Current.User.Identity.Name )作为可选项 UserNameSecurityToken 除了 X509CertificateSecurityToken MyCustomPrincipal 我的客户负责人 将安装。

    所以我花了五个小时试图实现这一点,在 various blogs 之后 TrustedSubsystemAuthorizationPolicy 完成了X509证书验证,安装了匿名 我的客户负责人 A. TrustedSubsystemImpersonationAuthorizationPolicy 接受带有空密码的用户名令牌并安装客户角色的 如果它看到匿名受信任子系统主体已经安装,并且 UserNameAuthorizationPolicy 它对未使用X509证书的其他端点执行了基于用户名和密码的常规验证。它成功了,而且非常棒。

    但是

    UserName 上 ClientCredentials ClientBase<T> ChannelFactory ,而不是特定的方法调用。

    你看,创建WCF客户端代理的新方法是 more expensive you might think . 我自己编写了一个快速而肮脏的应用程序来测试性能:new()启动一个新代理并调用一个方法十次大约需要6秒钟,而new()启动一个代理并只调用该方法十次大约需要1秒钟的三分之五。这只是令人沮丧的性能差异。

    所以我可以为客户端代理实现一个池或缓存,对吗?嗯,不,这不容易解决:客户端凭据信息处于通道工厂级别,因为它可能用于保护传输,而不仅仅是消息,而且一些绑定在服务调用之间保持实际传输的打开状态。由于客户端凭据对于代理对象是唯一的,这意味着我必须为当前网站上的每个用户都有一个唯一的缓存实例。内存中可能有很多代理对象,非常接近我一开始试图避免的问题!既然我必须去触摸 Endpoint 属性为可选的支持用户名令牌设置绑定,我不能利用 automatic channel factory caching

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

    Codeproject 和 random people on Google 这些都是很好的东西,因为它们帮助我快速启动和运行,即使在之后 running into a weird WCF bug when it comes to custom endpoint behaviors in configuration 端点 属性,我免费获得内置通道工厂缓存。自从 客户凭证 对于访问网站的任何用户来说都是一样的(事实上,它们始终是X509证书——只有消息本身中用户名头的值会发生更改),添加客户端代理缓存或代理池要简单得多。

    这就是我最后做的。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 而用于审计目的的日志记录现在很简单。

    我希望这能帮助一些可怜的人。

        2
  •  0
  •   Jay Jay    17 年前

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