基于SOAP扩展加强Web服务安全的方法
2009-10-14
作者:李 波1, 易 虎2
摘 要: 分析了SOAP消息结构和基于SOAP的Web服务特征,提出了一种加强Web服务安全性的方法。
关键词: Web服务 SOAP扩展 公钥 私钥
Web服务与现有的远程过程调用相比拥有较多优势:它是松散耦合的,与语言和平台无关;能够实现跨企业、跨因特网的远程调用。过去,由于缺少标准的通信底层结构,因而造成各个组织之间数据交换存在障碍。而简单对象访问协议(SOAP)解决了这个问题。它通过网络发送SOAP消息请求、调用并从一个应用程序返回SOAP消息结果。SOAP消息结构是用扩展标记语言(XML)表示的。XML已经发展成一种国际标准的网络语言,可以用于任何平台和系统。由于消除了基于RPC的系统之间数据交换的障碍,因而基于SOAP消息规范的Web服务得到广泛应用,但SOAP消息规范本身并没有直接提供任何机制去解决其安全性问题。本文将利用SOAP头和SOAP扩展加强Web服务的安全性,以满足企业对Web服务安全性的需求。
1 Web服务简介
Web服务通过使用SOAP消息处理数据交换和应用程序逻辑远程调用,使用基于XML的消息处理作为基本的数据通信方式。这样消除了使用不同组件模型、操作系统和编程语言的系统之间所存在的差异。Web服务的核心特征之一是服务的实现与使用之间的高度抽象化,通过基于XML的消息处理去创建和访问服务的机制。Web服务的使用者和提供者之间除输入、输出和位置之外无需互相了解其他信息。
2 SOAP消息规范
SOAP是一种基于XML消息结构的轻量级协议。SOAP的总体设计目标是使其尽可能地简单,并提供最少的功能。SOAP定义了一个消息处理框架,但它没有包含任何应用程序或传输语义,因此,SOAP是模块化的并具有很强的扩展性。
2.1 Web服务中SOAP的请求/响应模式
SOAP消息的请求/响应模式如图1所示。计算机A发送一个SOAP消息向计算机B请求某个服务,该消息表明了计算机A感兴趣的服务。计算机B收到请求后,翻译(反序列化)SOAP消息,并调用该服务请求。同时计算机B会创建一个SOAP响应,并把它发送回计算机A。计算机A收到消息后对其进行翻译,然后在屏幕上显示给用户。
2.2 SOAP消息的结构
SOAP消息由以下3个主要部分组成:
(1)SOAP包封装(envelope):它是SOAP头和SOAP体的包容器,可以包含XML命名空间、属性以及其他信息。
(2)SOAP头(header):这部分包含可选信息。可选的header元素可以包含不与特定消息直接相关的附加消息。按照此方式,Web服务可以通过SOAP头来提供某一类Web服务方法所需要的功能。例如,一个Web服务可能包含若干个Web服务方法,如果在调用每个Web服务方法之前需要验证调用者的身份,则可以将这些身份信息加载在SOAP头中。
(3)SOAP体(body):它包含实际的方法调用或响应数据。
2.3 SOAP的安全性
SOAP规范消息的加密/解密、认证和授权等安全机制一直受到人们的广泛关注。因为SOAP的一个很重要的设计目标就在于它的简单性,所以SOAP标准在制定规范时并没有过多考虑SOAP的安全性要求。SOAP消息框架本身没有直接提供任何机制去处理数据访问控制、机密性和完整性等功能。下面是一个用户信用卡查询结果的SOAP响应消息的样例(此处只显示SOAP体这一部分)。由于该SOAP消息在传输前没有经过加密,因此在传输过程中它很容易被非法用户访问到,必须采用一种机制对SOAP响应消息进行加密。
未加密的SOAP响应消息:
3 实现原理
这里使用公钥加密(public key encryption)的方法。这种加密机制中有2个不对称的密钥,即公钥和私钥。公钥用于对数据进行加密;私钥由用户密存,用于对已被公钥加密过的信息进行解密。在此应用中客户端(Web服务的使用者)首先要生成一对密钥(公钥和私钥)。由于服务端(Web服务的提供者)发送回客户端的部分数据需要在传输时进行加密保护,因此客户端将它的公钥(还可以附上它的身份信息以便在服务端进行身份验证)放在SOAP header中发送给Web服务端。Web服务端一旦得到公钥,就可以利用公钥对SOAP响应消息进行有选择性地加密,然后将加密后的SOAP消息返回给客户端;客户端收到加密的SOAP消息后用它的私钥来解密和读取数据。数据加密和解密的过程如图2所示。
在Web服务端发出SOAP响应消息之前要对敏感数据进行加密,可以利用SOAP扩展解决这个问题。SOAP扩展在客户端或服务器上处理消息时可以在特定阶段中检查或修改消息。当Web服务的HTTP处理器收到某个SOAP请求消息时,将把SOAP消息反序列化为对象,传递到Web方法中。在完成Web方法调用之后,对象结果又被序列化为SOAP响应消息,传给客户端。由于SOAP扩展允许在特定阶段访问或修改消息,因此服务器端就可以在对象结果被序列化为SOAP响应消息之后利用客户端传来的公钥进行选择性地加密,然后再将加密的SOAP响应消息传给客户端。客户端收到加密的SOAP响应消息后会把它反序列化为对象结果,然后利用私钥对对象结果中那些加密的属性或成员进行解密。
4 在.NET Framework下实现数据加密
在微软的.NET框架中,ProcessMessage是大多数SOAP扩展的核心,它在SoapMessageStage中所定义的每个阶段都会被调用。ChainStream为SOAP扩展提供访问和修改各阶段中SOAP消息的功能。在使用扩展时有4个阶段:BeforeDeserialize(反序列化之前)、AfterDeserialize(反序列化之后)、BeforeSerialize(序列化之前)和AfterSerialize(序列化之后)。调用任何一个Web服务方法都要经历这4个阶段。在4个阶段中,BeforeDeserialize阶段最早发生,在此阶段可以取得从客户端传来的公钥;AfterSerialize阶段最晚发生,在此阶段利用已取得的公钥对一些敏感数据进行加密。要实现对SOAP响应消息中的敏感数据进行加密,则需要访问序列化之后的SOAP响应消息。而在ChainStream方法中,可以访问包含SOAP请求或响应消息的内存缓冲区,这为修改SOAP响应消息的内存缓冲区提供了机会。
4.1 客户端关键代码
利用RSACryptoServiceProvider组件生成公钥和私钥,然后把公钥放在SOAP头中,其中PublicKeySoapHeader从SoapHeader类继承而来,这样便于扩展SOAP头,如增加身份验证信息和公钥等。下面代码仅列出了公钥字段。
RSACryptoServiceProvider rsa=new RSACryptoService-Provider();
string xmlpublicKey=rsa.ToXmlString(false); //生成公钥
string xmlPrivKey=rsa.ToXmlString(true); //生成私钥
EncryptServices services=new EncryptServices();
//创建代理服务对象
services.PublicKeySoapHeaderValue=new PublicKey-SoapHeader(); //创建SOAP头对象
services.PublicKeySoapHeaderValue.PublicXmlKey=xmlpublicKey; //通过SOAP头发送公钥
调用Web服务中的方法后对返回的结果对象中已加密部分利用私钥进行解密。下面是解密代码:
RSACryptoServiceProvider rsa=new RSACryptoServiceProvider();
rsa.FromXmlString(xmlPrivKey);
//根据私钥重新构造RSA对象
byte[ ] decryptedBytes=rsa.Decrypt(Convert.From-Base64String(encrypttext),false);//解密
4.2 服务端关键代码
在服务端,先利用SOAP扩展在BeforeDeserialize阶段从SOAP请求消息中取出客户端传来的公钥,然后利用SOAP扩展在AfterSerialize阶段对SOAP响应消息进行选择性的加密。在此代码中必须先继承SoapExtension类,然后重写它的ProcessMessage方法,具体代码如下:
public override void ProcessMessage(SoapMessage message)
{ switch(message.Stage)
{
case SoapMessageStage.BeforeDeserialize:
//反序列化之前阶段
ProcessSoapHeader(); //处理SOAP头方法,
//取出客户端传来的公钥
break;
case SoapMessageStage.AfterSerialize:
//序列化之后阶段
EncryptSoapMessage(); //调用加密方法,此时已序列化
//为XML格式,所以可选择性地对某些节点进行加密
break;
……
}
}
此外还要重写SoapExtension类的ChainStream方法,具体代码如下:
public override Stream ChainStream(Stream stream)
{ oldStream=stream; //读取SOAP请求或响应消
//息的内存流
newStream=new MemoryStream();
return newStream; //返回新的SOAP请求或
//响应消息的内存流
}
在BeforeDeserialize阶段,调用自定义的ProcessSoapHeader过程,通过对oldStream的访问可以取得客户端传来的公钥;在AfterSerialize阶段,调用自定义的EncryptSoapMessage过程,访问newStream并对敏感数据进行加密后把它重新写到newStream中。由于二个阶段内存流的格式都是SOAP消息格式,因此可以使用XmlDocument组件的Load方法加载内存流,这样就很容易找到目标节点进行选择性加密。最后使用XmlDocument组件的Save方法保存到内存流中。
下面是加密后的SOAP响应消息(在此只对AccountAmount元素的内容进行加密):
tRMQkIjy8OzGJmj8VISgNH1VvLFJOCP5M/5IjQHtzP4PvP-
TnjhmbTOG0Y9qdYmVil2aKvih/7sHMLeLpMxlhjlyMQb+
Uh/n0PB6vbysI+2K8NCiXxA40BiXuHvqLA5VWjY3-
LBwHXGsyuMdV+cDW+wBsdH2PleijdO5pJFBDO8qk=
即使该SOAP消息在中途被截获,由于没有私钥解密,得到的数据也是无效的。
5 结束语
SOAP消息仅仅是简单的文本,数据可能在经过网络时被截获、解读甚至修改。而这些数据代表的可能是一条客户记录、一个信用卡号或社会保险号码等敏感信息。本文就是先通过SOAP头传送公钥给Web服务端,然后利用SOAP扩展对SOAP消息进行加密。这样即使数据在网络传输过程中被截取,但由于没有私钥也无法对密文进行解密,从而保证数据不会被非法用户读取。此外,利用SOAP扩展可以有选择性地对某些数据进行加密,增强安全控制的灵活性并降低加密成本。本文只实现了对SOAP消息的加密。要确定SOAP消息是否在传输途中被恶意修改,可以将数字签名与安全套接字层(Secure Sockets Layer,SSL)结合实现不可抵赖性并确保数据的完整性。
参考文献
1 Microsoft Corporation.MSDN Library Visual Studio.Net.2003
2 Tabor R著,徐继伟译..NET XML WebServices.北京:机械工业出版社,2002
3 Thai T,Lam H Q著,王敏子译..NET框架精髓.北京:中国电力出版社,2001