SOAP 及其安全控制--转载
原文地址:http://my.oschina.net/huangyong/blog/287791
目录[-]
- 1. 基于用户令牌的身份认证
- 2. 基于数字签名的身份认证
- 3. SOAP 消息的加密与解密
- 4. 总结
通过上一篇文章,相信您已经学会了如何使用 CXF 开发基于 SOAP 的 WS 了。或许您目前对于底层原理性的东西还不太理解,心中难免会有些疑问:
什么是 WSDL?
什么是 SOAP?
如何能让 SOAP 更加安全?
我将努力通过本文,针对以上问题,让您得到一个满意的答案。
还等什么呢?就从 WSDL 开始吧!
WSDL 的全称是 Web Services Description Language(Web 服务描述语言),用于描述 WS 的具体内容。
当您成功发布一个 WS 后,就能在浏览器中通过一个地址查看基于 WSDL 文档,它是一个基于 XML 的文档。一个典型的 WSDL 地址如下:
http://localhost:8080/ws/soap/hello?wsdl
注意:WSDL 地址必须带有一个 wsdl
参数。
在浏览器中,您会看到一个标准的 XML 文档:
其中,definitions
是 WSDL 的根节点,它包括两个重要的属性:
- name:WS 名称,默认为“WS 实现类 + Service”,例如:HelloServiceImplService
- targetNamespace:WS 目标命名空间,默认为“WS 实现类对应包名倒排后构成的地址”,例如:http://soap_spring_cxf.ws.demo/
提示:可以在 javax.jws.WebService
注解中配置以上两个属性值,但这个配置一定要在 WS 实现类上进行,WS 接口类只需标注一个 WebService 注解即可。
在 definitions 这个根节点下,有五种类型的子节点,它们分别是:
- types:描述了 WS 中所涉及的数据类型
- portType:定义了 WS 接口名称(endpointInterface)及其操作名称,以及每个操作的输入与输出消息
- message:对相关消息进行了定义(供 types 与 portType 使用)
- binding:提供了对 WS 的数据绑定方式
- service:WS 名称及其端口名称(portName),以及对应的 WSDL 地址
其中包括了两个重要信息:
- portName:WS 的端口名称,默认为“WS 实现类 + Port”,例如:HelloServiceImplPort
- endpointInterface:WS 的接口名称,默认为“WS 实现类所实现的接口”,例如:HelloService
提示:可在 javax.jws.WebService
注解中配置 portName 与 endpointInterface,同样必须在 WS 实现类上配置。
如果说 WSDL 是用于描述 WS 是什么,那么 SOAP 就用来表示 WS 里有什么。
其实 SOAP 就是一个信封(Envelope),在这个信封里包括两个部分,一是头(Header),二是体(Body)。用于传输的数据都放在 Body 中了,一些特殊的属性需要放在 Header 中(下面会看到)。
一般情况下,将需要传输的数据放入 Body 中,而 Header 是没有任何内容的,看起来整个 SOAP 消息是这样的:
可见,HTTP 请求的 Request Header 与 Request Body,这正好与 SOAP 消息的结构有着异曲同工之妙!
看到这里,您或许会有很多疑问:
- WS 不应该让任何人都可以调用的,这样太不安全了,至少需要做一个身份认证吧?
- 为了避免第三方恶意程序监控 WS 调用过程,能否对 SOAP Body 中的数据进行加密呢?
- SOAP Header 中究竟可存放什么东西呢?
没错!这就是我们今天要展开讨论的话题 —— 基于 SOAP 的安全控制。
在 WS 领域有一个很强悍的解决方案,名为 WS-Security
,它仅仅是一个规范,在 Java 业界里有一个很权威的实现,名为 WSS4J。
下面我将一步步让您学会,如何使用 Spring
+ CXF
+ WSS4J
实现一个安全可靠的 WS 调用框架。
其实您需要做也就是两件事情:
- 认证 WS 请求
- 加密 SOAP 消息
怎样对 WS 进行身份认证呢?可使用如下解决方案:
1. 基于用户令牌的身份认证
第一步:添加 CXF 提供的 WS-Security 的 Maven 依赖
<dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-rt-ws-security</artifactId><version>${cxf.version}</version>
</dependency>
其实底层实现还是 WSS4J,CXF 只是对其做了一个封装而已。
第二步:完成服务端 CXF 相关配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:cxf="http://cxf.apache.org/core"xmlns:jaxws="http://cxf.apache.org/jaxws"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://cxf.apache.org/corehttp://cxf.apache.org/schemas/core.xsdhttp://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd"><bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 用户认证(明文密码) --><entry key="action" value="UsernameToken"/><entry key="passwordType" value="PasswordText"/><entry key="passwordCallbackRef" value-ref="serverPasswordCallback"/></map></constructor-arg></bean><jaxws:endpoint id="helloService" implementor="#helloServiceImpl" address="/soap/hello"><jaxws:inInterceptors><ref bean="wss4jInInterceptor"/></jaxws:inInterceptors></jaxws:endpoint><cxf:bus><cxf:features><cxf:logging/></cxf:features></cxf:bus></beans>
首先定义了一个基于 WSS4J 的拦截器(WSS4JInInterceptor),然后通过 将其配置到 helloService 上,最后使用了 CXF 提供的 Bus 特性,只需要在 Bus 上配置一个 logging feature,就可以监控每次 WS 请求与响应的日志了。
注意:这个 WSS4JInInterceptor 是一个 InInterceptor,表示对输入的消息进行拦截,同样还有 OutInterceptor,表示对输出的消息进行拦截。由于以上是服务器端的配置,因此我们只需要配置 InInterceptor 即可,对于客户端而言,我们可以配置 OutInterceptor(下面会看到)。
有必要对以上配置中,关于 WSS4JInInterceptor 的构造器参数做一个说明。
- action = UsernameToken:表示使用基于“用户名令牌”的方式进行身份认证
- passwordType = PasswordText:表示密码以明文方式出现
- passwordCallbackRef = serverPasswordCallback:需要提供一个用于密码验证的回调处理器(CallbackHandler)
以下便是 ServerPasswordCallback 的具体实现:
package demo.ws.soap_spring_cxf_wss4j;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.springframework.stereotype.Component;@Component
public class ServerPasswordCallback implements CallbackHandler {private static final Map<String, String> userMap = new HashMap<String, String>();static {userMap.put("client", "clientpass");userMap.put("server", "serverpass");}@Overridepublic void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];String clientUsername = callback.getIdentifier();String serverPassword = userMap.get(clientUsername);if (serverPassword != null) {callback.setPassword(serverPassword);}}
}
可见,它实现了 javax.security.auth.callback.CallbackHandler
接口,这是 JDK 提供的用于安全认证的回调处理器接口。在代码中提供了两个用户,分别是 client 与 server,用户名与密码存放在 userMap 中。这里需要将 JDK 提供的 javax.security.auth.callback.Callback
转型为 WSS4J 提供的 org.apache.wss4j.common.ext.WSPasswordCallback
,在 handle 方法中实现对客户端密码的验证,最终需要将密码放入 callback 对象中。
第三步:完成客户端 CXF 相关配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:jaxws="http://cxf.apache.org/jaxws"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd"><context:component-scan base-package="demo.ws"/><bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><!-- 用户认证(明文密码) --><entry key="action" value="UsernameToken"/><entry key="user" value="client"/><entry key="passwordType" value="PasswordText"/><entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/></map></constructor-arg></bean><jaxws:client id="helloService"serviceClass="demo.ws.soap_spring_cxf_wss4j.HelloService"address="http://localhost:8080/ws/soap/hello"><jaxws:outInterceptors><ref bean="wss4jOutInterceptor"/></jaxws:outInterceptors></jaxws:client></beans>
注意:这里使用的是 WSS4JOutInterceptor,它是一个 OutInterceptor,使客户端对输出的消息进行拦截。
WSS4JOutInterceptor 的配置基本上与 WSS4JInInterceptor 大同小异,这里需要提供客户端的用户名(user = client),还需要提供一个客户端密码回调处理器(passwordCallbackRef = clientPasswordCallback),代码如下:
package demo.ws.soap_spring_cxf_wss4j;import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.springframework.stereotype.Component;@Component
public class ClientPasswordCallback implements CallbackHandler {@Overridepublic void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];callback.setPassword("clientpass");}
}
在 ClientPasswordCallback 无非设置客户端用户的密码,其它的什么也不用做了。客户端密码只能通过回调处理器的方式来提供,而不能在 Spring 中配置。
第四步:调用 WS 并观察控制台日志
部署应用并启动 Tomcat,再次调用 WS,此时会在 Tomcat 控制台里的 Inbound Message 中看到如下 Payload:
可见,在 SOAP Header 中提供了 UsernameToken 的相关信息,但 Username 与 Password 都是明文的,SOAP Body 也是明文的,这显然不是最好的解决方案。
如果您将 passwordType 由 PasswordText 改为 PasswordDigest(服务端与客户端都需要做同样的修改),那么就会看到一个加密过的密码:
除了这种基于用户名与密码的身份认证以外,还有一种更安全的身份认证方式,名为“数字签名”。
2. 基于数字签名的身份认证
数字签名从字面上理解就是一种基于数字的签名方式。也就是说,当客户端发送 SOAP 消息时,需要对其进行“签名”,来证实自己的身份,当服务端接收 SOAP 消息时,需要对其签名进行验证(简称“验签”)。
在客户端与服务端上都有各自的“密钥库”,这个密钥库里存放了“密钥对”,而密钥对实际上是由“公钥”与“私钥”组成的。当客户端发送 SOAP 消息时,需要使用自己的私钥进行签名,当客户端接收 SOAP 消息时,需要使用客户端提供的公钥进行验签。
因为有请求就有相应,所以客户端与服务端的消息调用实际上是双向的,也就是说,客户端与服务端的密钥库里所存放的信息是这样的:
- 客户端密钥库:客户端的私钥(用于签名)、服务端的公钥(用于验签)
- 服务端密钥库:服务端的私钥(用于签名)、客户端的公钥(用于验签)
记住一句话:使用自己的私钥进行签名,使用对方的公钥进行验签。
可见生成密钥库是我们要做的第一件事情。
第一步:生成密钥库
现在您需要创建一个名为 keystore.bat 的批处理文件,其内容如下:
<!-- lang: shell -->
@echo offkeytool -genkeypair -alias server -keyalg RSA -dname "cn=server" -keypass serverpass -keystore server_store.jks -storepass storepass
keytool -exportcert -alias server -file server_key.rsa -keystore server_store.jks -storepass storepass
keytool -importcert -alias server -file server_key.rsa -keystore client_store.jks -storepass storepass -noprompt
del server_key.rsakeytool -genkeypair -alias client -dname "cn=client" -keyalg RSA -keypass clientpass -keystore client_store.jks -storepass storepass
keytool -exportcert -alias client -file client_key.rsa -keystore client_store.jks -storepass storepass
keytool -importcert -alias client -file client_key.rsa -keystore server_store.jks -storepass storepass -noprompt
del client_key.rsa
在以上这些命令中,使用了 JDK 提供的 keytool
命令行工具,关于该命令的使用方法,可点击以下链接:
http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html
运行该批处理程序,将生成两个文件:server_store.jks 与 client_store.jks,随后将 server_store.jks 放入服务端的 classpath 下,将 client_store.jks 放入客户端的 classpath 下。如果您在本机运行,那么本机既是客户端又是服务端。
第二步:完成服务端 CXF 相关配置
...
<bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 验签(使用对方的公钥) --><entry key="action" value="Signature"/><entry key="signaturePropFile" value="server.properties"/></map></constructor-arg>
</bean>
...
其中 action 为 Signature,server.properties 内容如下:
org.apache.ws.security.crypto.provider=org.apache.wss4j.common.crypto.Merlin
org.apache.ws.security.crypto.merlin.file=server_store.jks
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=storepass
第三步:完成客户端 CXF 相关配置
...
<bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><!-- 签名(使用自己的私钥) --><entry key="action" value="Signature"/><entry key="signaturePropFile" value="client.properties"/><entry key="signatureUser" value="client"/><entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/></map></constructor-arg>
</bean>
...
其中 action 为 Signature,client.properties 内容如下:
org.apache.ws.security.crypto.provider=org.apache.wss4j.common.crypto.Merlin
org.apache.ws.security.crypto.merlin.file=client_store.jks
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=storepass
此外,客户端同样需要提供签名用户(signatureUser)与密码回调处理器(passwordCallbackRef)。
第四步:调用 WS 并观察控制台日志
可见,数字签名确实是一种更为安全的身份认证方式,但无法对 SOAP Body 中的数据进行加密,仍然是“world”。
究竟怎样才能加密并解密 SOAP 消息中的数据呢?
3. SOAP 消息的加密与解密
WSS4J 除了提供签名与验签(Signature)这个特性以外,还提供了加密与解密(Encrypt)功能,您只需要在服务端与客户端的配置中稍作修改即可。
服务端:
...
<bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 验签 与 解密 --><entry key="action" value="Signature Encrypt"/><!-- 验签(使用对方的公钥) --><entry key="signaturePropFile" value="server.properties"/><!-- 解密(使用自己的私钥) --><entry key="decryptionPropFile" value="server.properties"/><entry key="passwordCallbackRef" value-ref="serverPasswordCallback"/></map></constructor-arg>
</bean>
...
客户端:
...
<bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><!-- 签名 与 加密 --><entry key="action" value="Signature Encrypt"/><!-- 签名(使用自己的私钥) --><entry key="signaturePropFile" value="client.properties"/><entry key="signatureUser" value="client"/><entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/><!-- 加密(使用对方的公钥) --><entry key="encryptionPropFile" value="client.properties"/><entry key="encryptionUser" value="server"/></map></constructor-arg>
</bean>
...
可见,客户端发送 SOAP 消息时进行签名(使用自己的私钥)与加密(使用对方的公钥),服务端接收 SOAP 消息时进行验签(使用对方的公钥)与解密(使用自己的私钥)。
现在您看到的 SOAP 消息应该是这样的:
可见,SOAP 请求不仅签名了,而且还加密了,这样的通讯更加安全可靠。
但是还存在一个问题,虽然 SOAP 请求已经很安全了,但 SOAP 响应却没有做任何安全控制,看看下面的 SOAP 响应吧:
如何才能对 SOAP 响应进行签名与加密呢?相信您一定有办法做到,不妨亲自动手试一试吧!
4. 总结
本文的内容有些多,确实需要稍微总结一下:
- WSDL 是用于描述 WS 的具体内容的
- SOAP 是用于封装 WS 请求与响应的
- 可使用“用户令牌”方式对 WS 进行身份认证(支持明文密码与密文密码)
- 可使用“数字签名”方式对 WS 进行身份认证
- 可对 SOAP 消息进行加密与解密
关于“SOAP 安全控制”也就这点事儿了,但关于“WS 那点事儿”还并没有结束,因为 RESTful Web Services 在等着您。如何发布 REST 服务?如何对 REST 服务进行安全控制?我们下次再见!
转载于:https://www.cnblogs.com/davidwang456/p/4343027.html
SOAP 及其安全控制--转载相关推荐
- mysql如何导入myeclisp中_MyEclipse搭建SSM框架(Spring+MyBatis+SpringMVC)
如何使用CSS绘制一个响应式的矩形 背景: 最近因为需要用到绘制类似九宫格的需求,所以研究了一下响应式矩形的实现方案. 有如下几种方案: 使用js来设置元素的高度 使用vw单位 div {width ...
- IREP_SOA Integration SOAP概述(概念)
20150827 Created By BaoXinjian 一.摘要 1. 什么是 SOAP? SOAP 指简易对象访问协议 SOAP 是一种通信协议 SOAP 用于应用程序之间的通信 SOAP 是 ...
- Windows Server 2008常见的安全设置
禁止来自外网的非法ping*** 我们知道,巧妙地利用Windows系统自带的ping命令,可以快速判断局域网中某台重要计算机的网络连通性;可是,ping命令在给我们带来实用的同时,也容易被一些恶意用 ...
- WDSL文件中的XML元素
理解起来其实很简单 Types指定类型,当然是在后面的Message中需要的类型 Message可以理解为函数中的参数,只不过如果一个函数如果有多个参数的时候应该吧这些参数定义到一个Message中而 ...
- 转gsoap使用总结
gsoap使用总结 >>用C实现WebService,gsoap是最好的选择了. >>快速开始 1. gsoap官网.遇到问题时,官网往往是最能提供帮助的地方. ...
- WebService 与 DCOM / Corba 是什么关系?
首先,COM/DCOM是组件/分布式组件模型标准,CORBA是分布式应用的服务标准.CORBA和DCOM为分布式应用程序建立服务和服务对象来执行客户端调用的服务.而SOAP是基于XML和HTTP的分布 ...
- PHP 编写和使用web服务 第一节
第一节,认识web服务 SOAP 简单对象访问协议(Simple Object Access Protocol,SOAP)是最健壮的Web服务协议.该协议可以发现应用程序功能,自动确定数据类型,具备数 ...
- (转)ASP.NET中常见文件类型及用途
从入门导师那继承来的习惯,也是加上自己的所谓经验判断,一直对WEB开发不太感冒,可惜呀,从业近二十年,还得从头开始对付HTML.CSS.JS.ASPX,以前的经验,用不上啦!!!先从好好学习ASPX开 ...
- [转]WebService 之 WSDL文件 讲解
原文地址:http://blog.csdn.net/tropica/archive/2008/11/02/3203892.aspx 恩,我想说的是,是不是经常有人在开发的时候,特别是和第三方有接口的时 ...
最新文章
- JavaScript的方法和技巧
- Hive之DDL数据定义
- SiteServer CMS 新版本 V6.15(2020年6月1日发布)
- 【C语言】C语言的数据类型
- Spring Boot 项目总是创建失败,这几个备选方案一定要收藏
- smarty模板引擎(一)基础知识
- 对于div背景颜色的透明
- 企业微信和钉钉的区别
- 2022最新阿里云域名注册和续费优惠口令及使用方法
- 怎么把好几行弄成一行_怎么把excel表格里多行变成一行数据|excel表格中让多行内容变成为一行...
- 操作系统安全防护技术
- 车内看车头正不正技巧_路边侧方位实用停车技巧,学会这一招,再也不担心车头刮到前车...
- 北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛(重现赛)题解
- 世界上第一胎电子计算机的主要构成原件,世界上第一台计算机的主要构成原件是什么...
- 艾伟_转载:如何开发绚丽、高效率的界面(Windows嵌入式系统)
- mac Anaconda matplotlib 中文乱码问题
- 如何写好科研论文(雨课堂)-期末考试答案
- 太强了,全面解析缓存应用经典问题
- 后台自动定时切换壁纸工具
- 关于浏览器插件的使用
热门文章
- python翻译成matlab_matlab语言转译成python
- mysql雨凇_Unity3D研究院之Unity中连接本地或局域网MySQL数据库(五十九) | 雨松MOMO程序研究院...
- tp5上传文件并获取文件路径_thinkphp表单上传文件并将文件路径保存到数据库中...
- flutter 局部状态和全局状态区别_Flutter状态管理
- python控制鼠标,如何在Mac中使用Python控制鼠标?
- cmd运行python服务器,python如何利用paramiko执行服务器命令
- 运行 命令 linux,Linux基本命令运行
- oracle ora 00279,ORA-01245、ORA-01547错误的解决
- Oracle的新建序列sequence
- php中文网数据库的搭建,【后端开发】php数据库中文乱码