深蓝海域KMPRO

保证.NET Web服务安全的必备知识

2002-09-17 09:10

保证.NET Web服务安全的必备知识


 
一、概述

谈到.NET Web服务,安全是最重要的问题之一。一旦把商务应用的一部分作为Web服务实现,就应当能够确保商务数据不会出现问题;类似地,如果商务应用依赖于Web服务,也应当确保发送给Web服务和从Web服务接收的数据不会受到任何威胁。本文总结了保证Web服务安全性的各种可用方案,针对可能遇到的各种情形提出一些建议。

首先,有两个概念是必须熟悉的:验证(Authentication),授权(Authorization)。如果把Web服务比喻为一幢大楼,验证是安全警卫的责任,他在人们进入大楼时检查每一个人的证件,阻止不带合法证件的人进入大楼。类似地,要访问Web服务,客户程序也必须通过身份验证。授权也就是访问控制,相当于各个房间上的锁起到的作用。要进入房间就必须有该房间的钥匙。类似地,要调用Web服务的某个功能,客户程序必须获得调用该功能的授权。本文的讨论主要面向Web服务安全的验证问题。

.NET Web服务和其他Web应用相似,它们的安全机制都可以分为两个大类:系统实现的安全机制,应用实现的安全机制。采用系统安全机制时,应用依赖于平台服务来提供必要的验证和授权功能;采用应用安全机制时,应用本身必须提供所有进行身份验证和授权的代码。系统安全机制的主要优点是,开发者不必编写和测试任何代码。IIS服务器提供了适合许多情形的大量验证方法(参见本文后面的参考资源)。然而,采用系统安全机制时,应用也同时受到平台安全服务固有局限的束缚。应用本身实现的安全机制灵活性最高,但代价是代码必须由开发者自己编写。

二、系统安全

图一显示了选择安全机制的决策树,可以帮助你选择合适的验证机制。从图一可以看出,Web服务的安全选项和其他Web应用的相似,选择安全机制的根据是网络环境和应用的需求。如果你正在构造的Web服务用于Intranet内部,则选择与网络拓扑有关。如果Web服务的用户与Web服务在同一个域之内,或者Web服务的用户在Web服务的域信任的域之内,可以使用Windows集成的验证机制(Integrated Windows Authentication)。在这种集成的安全机制下,Windows将使用当前登录用户的证书。对于最终用户来说,这是很方便的,因为用户一旦登录了Windows工作站,他的身份信息也将自动适用于Web服务应用。

 

如果用户不在一个被信任的域之内,下一步必须决定:是利用Windows帐号创建和管理用户帐号,还是使用应用本身的帐号。如果使用Windows帐号,创建和维护用户帐号的工具是免费的。然而,在这种情况下,用户访问应用时实际上是利用IIS登录到了Windows服务器或Windows域。

此外,一旦依赖于Web服务器上本地定义的用户帐号,则增加额外的Web服务器时会出现很多麻烦,必须把所有的帐号复制到每一个新增的Web服务器上。基于应用的帐号要灵活得多,具有更好的可伸缩性,因为在这种情况下,用户帐号可以保存到一个可供所有Web服务器访问的中心数据库。但这时,所有创建和维护用户帐号的工具都必须由开发者自己编写。

让Windows管理用户帐号有两种可能的验证方式。如果系统中使用了Windows 2000域控制器,且所有的客户都使用IE 5或更高版本,则可以使用Digest验证。Digest验证是IETF(Internet Engineering Task Force)的标准验证机制。在这种验证方式下,客户程序(IE 5)在把用户证书发送给服务器(IIS 5)之前,将先对证书进行加密。

如果系统不能满足采用Digest验证必须符合的全部要求,另一种选择是Basic验证。Basic验证是一个比较老的IETF标准。采用这种验证方式时,客户程序以明文的形式将用户证书发送给服务器。Basic验证方式经常和安全套接字层(Secure Socket Layer,SSL)一起使用,对客户端和服务器端发送的所有数据都加密,以免用户证书被窃取。但是,如果在Intranet环境内使用Basic验证,你可能不需要使用SSL,以避免SSL带来的性能影响。

对于在Internet上运行的Web服务,选择安全机制的过程与Intranet环境下用户在非安全域的情形类似。主要的区别在于,一旦决定采用Basic或应用自身实现的验证机制,通常还需要加上SSL以确保用户证书的安全,因为用户证书要通过Web传递。这里的决定因素是用户密码的重要程度。例如,如果用户验证只是为了便于提供个性化的内容,根据用户所在城市提供天气信息,则密码被窃取的可能性不大。另一方面,如果验证身份是为了让用户进入股票交易系统,用户可以在该系统内买卖股票,则最好使用SSL。

无论是使用集成的安全验证,还是使用Basic、Digest安全验证,都要对IIS服务器进行配置,就象为普通Web应用配置IIS服务器一样。此外,ASP.NET也要经过配置。因为.NET Web服务运行在ASP.NET框架的基础上,而ASP.NET又运行在IIS的基础上,要保证Web服务的运行安全,所有这些地方都必须正确地配置(参见图二)。

MSDN主题“ASP.NET Data Flow”(参见本文后面的参考资源)揭示了在两种常见的情形下,IIS和ASP.NET如何一起协作保证ASP.NET应用的安全。ASP.NET验证机制在Web.config文件中配置,Visual Studio.NET把这个XML文件放在包含Web服务的应用的根下。在Web.config文件中,authentication元素的mode属性可以是下面四个值之一:None,Windows,Forms,或Passport。例如,在下面的例子中,“None”选项关闭了ASP.NET安全验证:

<authentication mode="None"/>

把验证模式设置为Windows意味着应用依赖于IIS实施验证,对应于本文前面所谓的系统安全机制。把IIS配置成除匿名访问之外的任何其他验证形式时我们使用该选项。也就是说,这个选项本身不保证对用户进行身份验证,使用该选项时还应当把IIS设置成除匿名访问之外的其他验证形式。

Forms(表单)验证以前也叫做Cookie验证,实际上是系统验证和应用验证之间的一种混合验证类型。表单验证类型向用户显示一个表单,要求用户输入名字和密码。实际的验证过程由开发者编写的代码完成,例如通过一个用户信息数据库完成。用户顺利通过身分验证之后,应用把一个唯一的键值以Cookie的形式写入到客户端。客户程序在每一个后继的请求中,把这个Cookie发送给服务器,服务器负责在处理请求之前检验这个Cookie。这种验证类型不适用于Web服务,因为一般而言,Web服务的客户程序不知道如何处理登录表单。如果能够为Web服务编写一个定制的代理服务器,则只要提供了提交登录表单(带有用户名字和密码)的代码和处理Cookie的代码,表单验证或许也能够适用于这种场合。另外值得指出的是,表单验证有一个缺点,这就是用户名字、密码和Cookie都是以明文的形式传递,恶意的黑客可能窃取这些信息。

最后,Passport验证使用Microsoft的Passport服务。Passport相当于Web上的Windows集成安全机制,它允许用户在Web会话期间登录,然后从一个支持Passport的站点转到另一个支持Passport的站点,且无需在每一个站点上反复登录。Passport验证也是以表单为基础的:用户必须在一个HTML表单中输入名字和密码,然后把这些信息发送给Passport服务器。

如果你选择使用Passport,可以用www.passport.com/sdkdocuments/sdk21/default.htm 的Passport SDK把用户的证书发送到Passport服务。因此,除非Web服务的客户程序知道如何处理Passport登录请求,否则,在Web服务环境下使用Passport验证是不可能的。

如果依赖于IIS实施验证,Web服务的客户程序可以把用户的证书附带在发送给服务的HTTP请求中发送。当客户程序也建立在.NET框架上时,这种处理方式尤其方便。首先创建一个NetworkCredentials对象,把用户名字(用户ID)和密码传入该对象的构造函数。然后,利用这个NetworkCredentials对象设置Web服务代理的Credentials属性。下面是一个使用Web服务的例子,Web服务的名字是Invoicing:

Dim inv As New com.Invoicing() inv.Credentials =New NetworkCredential ("AppUser","password ") '在下面这个调用中证书被传递给Web服务器 inv.DoWork()

三、应用安全

从上面的说明可以看出,每一种基于IIS的验证方式都有各自的局限。由于这些局限的存在,再加上应用往往有一些特殊的要求,所以许多时候你会发现让应用自己处理用户验证是最好的选择。

不管应用验证机制的具体实现方式如何,从体系上看它们实际上都是一样的。首先,客户程序发送用户名字和密码,然后应用通过这些信息验证用户的身份。例如,应用可能在数据库中查找客户程序提供的用户名字,然后比较客户程序提供的密码和数据库中保存的密码。

如果用户名字和密码都是正确的,应用会生成一个键(一个唯一的字符串形式的Bit序列),然后把这个键发送给客户程序,同时在一个键缓冲区中保存这个键。此后,客户程序在每一个请求中把该键发送给应用。对于每一个客户程序发送的请求,应用在键缓冲区中检查客户程序提供的键,根据检查结果判断客户是否已经通过身份验证。

实现这种验证机制时有几个方面的问题需要考虑。首先,必须考虑在网络上传输用户证书和键时,如何保证它们的安全。如果Web服务的客户程序是定制的,则把这些敏感信息发送给服务器之前,可以让客户程序先加密这些信息。然而,要正确地实现这一点,还有一些问题需要解决,例如:如何同步客户程序和服务器使用的密匙,如何在客户端保证密匙的安全。许多时候,你应该考虑用SSL加密客户程序和服务器之间的通信过程,而不是自己实现加密。尽管使用SSL会带来一定的性能影响,但对于大多数商务应用来说,客户程序和服务器之间传送的数据都是不可泄漏的,安全远比有限的性能影响重要。

大多数情况下,Web服务上必须有一个支持客户程序登录Web服务的方法:

<WebMethod()>Public Function Logon(ByVal userId As _ String,ByVal pwd As String)As String Dim authKey As String If SecurityMgr.Logon(userId,pwd,authKey)Then Return authKey Else Throw New Exception( "用户名字或密码错误!") End If End Function

该方法通过Users数据库表检查用户名字(userID)和密码的合法性。如果用户名字和密码是合法的,该方法随机生成一个键,并把这个键保存到名为AuthKeys的数据库表。另外,该方法还在名为LastUsed的字段中保存当前的日期和时间。此后,程序可以利用LastUsed字段确定键是否已经过期。

接下来还要考虑如何在每一个后继请求中发送键。一种可用的方法是,可以在每一个Web服务的方法中加入一个表示键的字符串参数,指示客户程序提供这个参数。但这里推荐使用SOAP头。SOAP头是一种扩展机制,允许为SOAP协议增加新的功能。在.NET框架下,定义和使用SOAP头是很方便的。同样,用SOAP Toolkit 2.0处理SOAP头也很方便。如果客户程序使用的是其他SOAP工具,请首先检查从客户程序发送SOAP头是否简单易行。

要使用SOAP头,首先要创建一个从SoapHeader派生的类,加入必要的公用成员:

Public Class authHeader Inherits SoapHeader Public authToken As String End Class

然后,声明该类的实例为Web服务类的一个成员变量:

<WebService(Namespace:= "......")>_ Public Class Invoicing Inherits System.Web.Services.WebService '身份验证用的SOAP头 Public authHdr As authHeader

接下来是关键部分。对于Web服务中每一个需要该SOAP头的方法(对于本例,所有的方法都需要该SOAP头,因为它包含了身份验证的键),加上一个SoapHeader属性:

<WebMethod(),SoapHeaderAttribute("authHdr", Direction:=SoapHeaderDirection.In, Required:=True)>_ Public Function SubmitInvoice(ByVal invoiceDoc As _ String) As Integer

.NET框架从SOAP请求提取出SOAP头,并用它填写authHdr的authToken成员。Web服务的代码读取authHdr.authToken,检查键是否合法。你可以为AuthKeys表编写一个存储过程检查键的合法性,如果键是合法的,则更新LastUsed字段以反映最新的使用日期和时间。

在客户端,已经生成的Web服务代理有一个名为authHeaderValue的属性。客户程序必须把该属性设置为一个新的authHeader实例,把它的authToken属性设置为调用LogOn返回的键:

Private _inv As com.Invoicing Private Sub btnLogOn_Click(ByVal sender _ As System.Object, _ ByVal e As System.EventArgs)Handles btnLogOn.Click _inv =New com.Invoicing() '登录到Web服务 Dim authKey As String =_inv.Logon( _ txtUserName.Text,txtPwd.Text) '设定验证信息 _inv.authHeaderValue =New com.authHeader() _inv.authHeaderValue.authToken =authKey End Sub

客户程序只有在首先实例化Web服务代理之后,才能执行上述步骤。此后,客户程序就可以保留和使用该代理的实例,且不必再每次都重新登录或设置SOAP头信息了。

利用SOAP头发送验证信息能够确保Web服务代码简洁,能够允许客户程序一次设置SOAP头信息之后反复使用。这种验证体系思路清楚,且使得客户程序和Web服务的代码具有更好的可读性。

四、应付“拒绝服务”攻击

在为Web服务实现安全机制之前,有两个问题必须清楚。首先,如果安全子系统利用数据库连接之类的紧缺资源来确定发出请求的用户是否具有合法身份,系统将面临“拒绝服务”攻击。要避免这个问题,必须修改前面给出的示范验证系统。

一种修改的办法是,创建一个验证键的hash码,把键发送到客户端之前,把hash码连接到键的后面。这样,在每一个客户请求中,可以得到键以及附加到键后面的hash码。你可以方便地重新为客户程序返回的键构造出hash码,把这个hash码和客户程序返回的hash码比较,如果两者不能匹配,则说明客户程序返回的键非法,这时就不必再访问数据库验证键的合法性了。在MSDN的一篇文章中,Mary Kirtland详细地说明了这种机制,介绍了MSDN小组如何在“Favorites”Web服务中使用这种机制(参见参考资源“Authentication and Authorization”)。

其次,在使用其他人开发的Web服务时应该慎重。公用Web服务可能由于“拒绝服务”攻击或其他原因而关闭,如果在Web应用中使用了公用的Web服务,应当保证Web应用能够妥善地处理这类情形。编写Web服务的客户程序时应该考虑到最坏的情形。如果客户程序是一个运行关键任务的应用,则不应该依赖于公用Web服务执行其核心任务。

总而言之,实现Web服务安全机制的办法不止一种,选择时必须考虑多种因素,包括网络环境和客户端情况。自己实现验证机制具有最好的灵活性。只要选择了一种合适的机制,Web服务安全可能变得既容易实现,又容易使用。

相关推荐