· 问题
关于 web service 就不用多说了。以下说明我们在使用web service经常碰到的问题:
1、web service接口的安全性:web service的接口是直接暴露在web(虽然可以通过一些方式来验证访问者的身份,但实现起来都比较麻烦,或者性能不佳),另如何防止重放攻击也是个问题;
2、web service数据在web上传输是使用明文的方式,数据容易被非法拦截或篡改;
3、web service需要传输较多的数据时的性能需要如何优化。
· 解决
使用 soap extension来解决以上的问题:
1、在soap头加入身份认证信息(称为Passport),Passport身份的一个凭证,在客户端和服务器端都有保存,验证时必须相匹配。passport在soap头是个报文数据,包含执行身份验证需要的信息。passport使用事务标识来防止重放攻击,即有服务端发出事务标识供客户端使用,客户端发出请求时必须包含该事务标识,当服务端响应后,服务端注销原来的事务标识并生成新的事务标识;
2、数据保护,使用数据摘要或对数据进行加密;
3、数据压缩,在数据传输前对数据进行压缩。
· 、概述
本组件在实现上是通过使用Soap扩展(Passport协议)和SecurityExtension协议来实现对Web Service 的扩展。组件提供3个功能:
1、身份认证
身份认证由Passport协议实现的。登陆、登出以及结束异步事务是通过专门的Passport服务接口(WebService)发送 Passport包实现的。服务请求是通过加入自定义的Soap头(包含有Passport包)来实现的。
客户端要请求WebService 服务必须先在本地创建执照,并发出登陆请求时,服务端接受到登陆请求,注册客户执照,并把更改后的执照返回给客户段。在以后的服务请求中,根据Passport协议个规定,客户端和服务端不断修改执照信息来完成身份的验证。
身份认证中在服务器端保存的执照信息采用缓存的方式,当超过一定时间将被注销。
1.1、简单执照验证
简单执照密码验证包括登陆、请求、登出三个部分。
登陆在实现上是向服务器专门的Passport访问通道(WebService)发送加密过身份认证的信息(包括用户名、密码、登记号)。服务器解析身份认证信息,并验证用户名和密码(这个验证过程实施者必须重写),当用户名和密码验证通过后,服务端根据登记号在服务器注册其登记号,如果该登记号已经存在,这登陆失败。注册完登记号,服务器将产生一个执照(Guid同登记号),加密后返回给登陆请求者,这时完成登陆。之后客户端发出的请求必须包含此执照,直至登出。
服务请求过程中,客户端发出的服务请求中包含服务器授予的执照,服务器接收到请求后,首先验证此执照的正确性。通过验证后,执行请求的内容,返回给客户端。
登出时,服务器将注销授予给客户端的执照,客户端如果还要服务请求必须先登陆。
1.2、事务执照验证
事务执照验证包括登陆、服务请求、登出三个部分。
登陆请求和简单执照验证一样。不同的是服务器注册登记号后,产生一个执照,该执照包含一个事务登记号,事务登记号只能使用一次(同步的状态下)。服务器把执照返回给客户端完成登陆。
服务请求的过程分为2种类型:一种是同步的方式,即客户端发出一个请求后,在接受到服务器响应后再发下一个请求;另一种是异步的方式,即客户端同时向服务器发出多个请求。
同步的方式中,当客户端发出服务请求中包含服务器授予的执照,如果请求中的事务ID尚未被注册,服务器则拒绝服务。事务ID验证通过后,服务器更新事务ID并销毁原事务ID。接着执行请求内容,完成客户端的请求,并发送更新后的执照给客户端。
异步的方式中,客户端在服务器授予的执照加入异步标识号(称异步ID),发送给服务器,服务端接收到这样的执照后,先执行事务验证。事务验证通过后,验证此事务的异步 ID是否已经被注册,如果已经被注册,则拒绝服务。异步验证通过后,服务端注册异步ID,执行请求内容后把执照在发回给客户端。注意,执照并没有发生改变,同一批异步请求共用一个事务ID。当客户端确认要结束本次的异步请求时(客户端必须确保所有的线程的请求已经结束),发送结束异步请求信息(包括事务ID和结束标志)。服务端将注销该异步请求的所有信息,并生成新的事务ID和更新执照。
登出时,客户端发送登出请求。服务端将注销所有本次登陆在服务端注册的信息,完成登出。
2、数据保护
通过数据摘要的方式来验证数据的完整性,同时也可以使用数据加密的方式防止重要的数据被非法截获。
3、数据压缩
对webservice的请求和响应的数据进行压缩,默认使用rfc1951描述的进行压缩。实施者可以重写压缩方式。
· 细节
1、Passport
Passport为客户端进行web service 请求和确认服务响应的一种证书。Passport往返于客户端和服务器之间(通过PassportInfo),而且双方在本地都通过管理器保存有Passport的副本。Passport的生成是服务器根据客户端发出登记号产生的,简单执照验证中,Passport一经生成就不在修改,客户端可以重复使用直到登出。而事务执照验证中,Passport中的事务ID将不断改变,这个改变是由服务器发起的,当服务器接受到Passport,验证通过后(登记号和事务ID)将生成新的事务ID并销毁原来的事务ID。对于异步多个请求的方式,服务器将认为是同一个事务,而使用异步ID加以注册。
执照信息(PassportInfo)是保存在Passport服务接口的参数或者命名为Passport的Soap头里,内容是Passport协议报文数据经过加密和Base64编码过的数据。报文内容如下:
Passport协议
报文:
报文标记开头(PS) |
D |
保留 |
验证类型 |
请求类型 |
PassportID(16字节) | ||||
Username(16字节)(可选) | ||||
Password(16字节)(可选) | ||||
TransactionID(16字节)(可选) | ||||
LastTransactionID(16字节)(可选) | ||||
AsynchronismID(16字节)(可选) |
报文标记开头:16位,固定为PS。
D:1位,报文发送者(0客户端/1服务端)
保留:7位
验证类型:4位
0x0:无验证
0x1:简单执照验证
0x2:事务执照验证
请求类型:4位
请求类型 |
说明 |
验证类型 |
D为0时 |
D为1时 |
0x1 |
登陆请求 |
0x1 |
Username、Password必须 |
|
0x2 |
TransactionID | |||
0x2 |
服务请求 |
0x1 |
|
|
0x2 |
TransactionID 必须 |
TransactionID,LastTransactionID 必须 | ||
0x4 |
异步服务请求 |
0x2 |
TransactionID,AsynchronismID必须 |
TransactionID,AsynchronismID必须 |
0x5 |
结束异步服务请求 |
0x2 |
TransactionID |
TransactionID,LastTransactionID 必须 |
0xf |
登出请求 |
0x1 |
|
|
0x2 |
|
|
PassportInfo先由客户端发出,服务器接收后,进行响应和修改,返回给客户端,客户端对接收到的 PassportInfo 进行验证(PassportID和LastTransactionID,并更新本地的Passport。
2、SoapExtension
Soap扩展从System.Web.Services.Protocols.SoapExtension类派生的,可以在客户端或服务器上处理消息时在特定阶段中检查或修改消息。GetInitializer 和 Initialize 方法提供其他可用机制,用于初始化 SOAP 扩展以增强性能。ProcessMessage 是大多数 SOAP 扩展的核心,原因是该方法在 SoapMessageStage 中定义的每一个阶段都被调用,从而使 SOAP 扩展得以执行所需的该特定 SOAP 扩展的行为。对于需要修改 SOAP 请求或 SOAP 响应的 SOAP 扩展,ChainStream 提供一个机会以接收要通过网络发送的建议数据。(详见SDK文档)
对数据进行操作的顺序:
客户端发出请求:打包加入执照 – 数据摘要/加密– 数据压缩
服务器接受请求:数据解压 – 数据解密/验证摘要 – 执照验证
服务器响应请求:打包加入执照 –数据摘要/加密– 数据压缩
客户端接受响应:数据解压 – 数据解密/验证摘要 – 执照确认/更新
数据摘要默认使用内置的MD5算法。
数据加密默认使用内置的DES算法进行加密,实施者可以重写该方法。
数据压缩默认使用第三方组件ICSharpCode.SharpZipLib 进行压缩,使用 rfc1951中的压缩描述,实施者可以重写该方法。
其中数据摘要/加密、数据压缩是发生在soap数据序列化之后或反序列化之前,而打包加入执照、执照确认/更新是发生在soap数据反序列化之后或序列化之前。
数据摘要信息是保存在soap数据流的前面部分。具体格式如下:摘要长度(32位)+摘要+数据。
客户端的发出请求的时候, ServerSecurityExtension先检查本地执照,如果本地没有执照,将创建登陆执照,发出登陆请求,完成登陆后,再发出请求。用户确认要登陆的时候发出登出请求。登陆和登出请求由PassportService WebService类管理。
SecurityExtension 协议定义:
报文:
报文标记开头(SE) |
D |
保留 | ||
压缩类型 |
压缩参数 |
保护类型 |
保护参数 | |
数据摘要+数据 | ||||
报文标记开头:16位,固定为SE。
D:1位,报文发送者(0客户端/1服务端)
保留:15位
压缩类型:4位
已定义的类型:
0x01 组件内置的压缩方案
压缩参数:12位
与压缩类型对应的压缩的相关参数
0x01方案(4位):
0x0:不压缩;
0x1:最快;
0x2:较快;
0x3:默认;
0x4:较好;
0x5:最好;
保护类型:4位
已定义的类型:
0x0 未实现保护
0x1 数据摘要
0x2 数据加密
保护参数:12位
与保护类型对应的保护的相关参数
0x0方案(12位)
0x1方案(12位),前4位为类型,后8位为摘要数据长度:
0x0:使用内置的MD5算法;
0x1:使用.Net框架的HMACSHA1;
0x2:使用.Net框架的MACTripleDES;
0x3:使用.Net框架的MD5CryptoServiceProvider;
0x4:使用.Net框架的SHA1Managed;
0x5:使用.Net框架的SHA256Managed;
0x6:使用.Net框架的SHA384Managed;
0x7:使用.Net框架的SHA512Managed;
0x2 方案(12位),前4位为类型,后8位空
0x0:使用内置的DES算法;
0x1:使用.Net框架的DESCryptoServiceProvider;
0x2:使用.Net框架的RC2CryptoServiceProvider;
0x3:使用.Net框架的RijndaelManaged;
0x4:使用.Net框架的TripleDESCryptoServiceProvider;
· 配置
SecurityExtension 的配置主要是针对一些算法和参数的配置。可以在.config文件定义全局的配置,也可以在SecurityExtensionAttribute定义某个webservice接口的配置。
1、服务端
<configSections>
<sectionGroup name="SecurityExtension">
<section name="Configure" type="MFW.Component.SecurityExtension.SecurityExtensionConfigureHandler,SecurityExtension" />
</sectionGroup>
</configSections>
<appSettings>
<add key="SecurityExtensionDebugOutputDirectory" value="D:\"></add>
</appSettings>
<SecurityExtension>
<Configure>
<PassportLevel value="2"></PassportLevel>
<Sponsor>
<Passport>
<Handler type="MFW.Component.SecurityExtension.PassportModule.DefaultPassporter,SecurityExtension"></Handler>
<KeyData value="suXh/uz4x7w="></KeyData>
</Passport>
<Compress>
<Handler type="MFW.Component.SecurityExtension.DefaultCompresser,SecurityExtension"></Handler>
<Level value="0"></Level>
</Compress>
<Protect>
<Handler type="MFW.Component.SecurityExtension.DefaultProtecter,SecurityExtension"></Handler>
<Level value="0"></Level>
<KeyData value="suXh/uz4x7w="></KeyData>
</Protect>
</Sponsor>
<Supporter>
<Passport>
<Handler type="MFW.Component.SecurityExtension.PassportModule.DefaultPassporter,SecurityExtension"></Handler>
<KeyData value="suXh/uz4x7w="></KeyData>
</Passport>
<Compress>
<Handler type="MFW.Component.SecurityExtension.DefaultCompresser,SecurityExtension"></Handler>
</Compress>
<Protect>
<Handler type="MFW.Component.SecurityExtension.DefaultProtecter,SecurityExtension"></Handler>
<KeyData value="suXh/uz4x7w="></KeyData>
</Protect>
</Supporter>
</Configure>
</SecurityExtension>
说明:
SecurityExtensionDebugOutputDirectory 保存服务端接收和响应的数据目录,用于调试用。
Sponsor 发出者的配置,即对应与 Supporter 支持者,一个匹配的对。Sponsor 用于数据的编码,Supporter用于数据的解码。
2、客户端
<configSections>
<sectionGroup name="SecurityExtension">
<section name="Configure" type="MFW.Component.SecurityExtension.SecurityExtensionConfigureHandler,SecurityExtension" />
<section name="PassportAccount" type="MFW.Component.SecurityExtension.PassportModule.PassportAccountHandler,SecurityExtension" />
<section name="AccessService" type="MFW.Component.SecurityExtension.AccessServiceHandler,SecurityExtension" />
</sectionGroup>
</configSections>
<SecurityExtension>
<Configure>
<PassportLevel value="2"></PassportLevel>
<Sponsor>
<Passport>
<Handler type="MFW.Component.SecurityExtension.PassportModule.DefaultPassporter,SecurityExtension"></Handler>
<KeyData value="suXh/uz4x7w="></KeyData>
</Passport>
<Compress>
<Handler type="MFW.Component.SecurityExtension.DefaultCompresser,SecurityExtension"></Handler>
<Level value="0"></Level>
</Compress>
<Protect>
<Handler type="MFW.Component.SecurityExtension.DefaultProtecter,SecurityExtension"></Handler>
<Level value="0"></Level>
<KeyData value="suXh/uz4x7w="></KeyData>
</Protect>
</Sponsor>
<Supporter>
<Passport>
<Handler type="MFW.Component.SecurityExtension.PassportModule.DefaultPassporter,SecurityExtension"></Handler>
<KeyData value="suXh/uz4x7w="></KeyData>
</Passport>
<Compress>
<Handler type="MFW.Component.SecurityExtension.DefaultCompresser,SecurityExtension"></Handler>
</Compress>
<Protect>
<Handler type="MFW.Component.SecurityExtension.DefaultProtecter,SecurityExtension"></Handler>
<KeyData value="suXh/uz4x7w="></KeyData>
</Protect>
</Supporter>
</Configure>
<PassportAccount username="admin" password="8888"></PassportAccount>
<AccessService type="SecurityExtensionClientTest.PassportAccess.PassportAccess,SecurityExtensionClientTest"></AccessService>
</SecurityExtension>
</configuration>
说明:
PassportAccount 帐户和密码
AccessService Passport服务通道
· 使用的注意点
1、 在服务器端加入的webservice 接口加入 [SecurityExtension(Port=PortType.Server)],其中 Port 必须为 PortType.Server。
2、 在服务器端一个webservice页PassportAccess,该类从 PassportAccessService 派生,并实现 ValidateAccount 方法。
3、 在 web.config 加入关于 SecurityExtension 的配置项。
4、 客户端引用 PassportAccess 页,并在代理类加入 IPassportAccess 接口。
5、 在使用 SecurityExtension 的代理类加入 [SecurityExtension(Port=PortType.Client)] ,其中 Port 必须为 PortType.Client。
6、 在app.config 加入关于 SecurityExtension 的配置项。
7、 另组件还处于测试阶段,如果大家发现什么问题或者有什么建议请告诉偶哦。 :)
8、 现在组件只使用内置的算法对数据进行处理,并不支持协议上所定义的。
9、 PS,偶的描述可能不是很清晰哦~~,请大家见量。
· 组件代码 V0.1
组件包含两个版本的,一个是framework1.1,另一个是精简版的(只支持client端的,现在有个项目使用WINCE,因此才做了个精简版的)。
下面文件包含组件的源代码、精简版源代码、类库文档和一个简单的demo。