1.Web service的概念
什么是WebService呢?从表面上看,Web Service就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API。这就是说,你能够用编程的方法通过Web调用来实现某个功能的应用程序。从深层次上看,Web Service是一种新的Web应用程序分支,它们是自包含、自描述、模块化的应用,可以在网络(通常为Web)中被描述、发布、查找以及通过Web来调用。一旦部署以后,其他Web Service应用程序可以发现并调用它部署的服务。Web Service是基于网络的、分布式的模块化组件,它执行特定的任务,遵守具体的技术规范,这些规范使得Web Service能与其他兼容的组件进行互操作。它可以使用标准的互联网协议,像超文本传输协议HTTP和XML,将功能体现在互联网和企业内部网上。Web Service平台是一套标准,它定义了应用程序如何在Web上实现互操作性。你可以用你喜欢的任何语言,在你喜欢的任何平台上写Web Service。Web Service是构建互联网分布式系统的基本部件。"网络服务"(WebService)的本质,就是通过网络调用其他网站的资源。
举例来说,写一个“四川大地震图片墙”,它能动态显示关于四川地震的最新图片。但是,所有的图片都不是储存在自己的服务器上,而是来自flickr.com。只需要发出一个动态请求,要求flickr.com向自己提供图片。这种情况下,flickr.com提供的就是一种Web service。如果把图片都存放在本地服务器,不调用flickr.com,那么我就是在使用“本地服务”。
所以,Webservice让你的网站可以使用其他网站的资源,比如在网页上显示天气、地图、twitter上的最新动态等等。
2.Web Service架构和云
如果一个软件的主要部分采用了“网络服务”,即它把存储或计算环节“外包”给其他网站了,那么我们就说这个软件属于Web Service架构。
Web Service架构的基本思想,就是尽量把非核心功能交给其他人去做,自己全力开发核心功能。比如,如果你要开发一个相册软件,完全可以使用Flickr的网络服务,把相片都储存到它上面,你只要全力做好相册本身就可以了。总体上看,凡是不属于你核心竞争力的功能,都应该把它“外包”出去。
最近很红的“云计算”(cloudcomputing)或者“云服务”(cloud services),实际上就是Web Service的同义词,不过更形象一些罢了。它们不说你把事情交给其他计算机去做,而说你把事情交给“云”去做。
3.Web Service的优势
Web Servcie最主要的优点是,使用不同程序和在不同系统平台上开发出来的程序,都可以相互通信。SOAP作为Web Service的基本通信协议,简单,投入和使用的代价也很小。Web Service使用标准的互联网协议-XML、HTTP和TCP/IP。
Web Service有以下的优越性:
1)平台无关。不管你使用什么平台,都可以使用Web service。
2)编程语言无关。只要遵守相关协议,就可以使用任意编程语言,向其他网站要求Web service。这大大增加了web service的适用性,降低了对程序员的要求。
3)对于Webservice提供者来说,部署、升级和维护Web service都非常单纯,不需要考虑客户端兼容问题,而且一次性就能完成。
4)对于Webservice使用者来说,可以轻易实现多种数据、多种服务的聚合(mashup),因此能够做出一些以前根本无法想像的事情。
4.Web Service 三个基本技术
Web Service通过标准通信协议,在互联网上发布有用的程序模块(以服务的方式),目前大部分是用SOAP来作通信协议。
Web Service提供一份详细的接口说明书,来帮助用户构建应用程序,这个接口说明书叫作WSDL(Web Service Description Language)。
通常已发布的WebService要注册到管理服务器,这样便于使用者查询和使用。这个是通过UDDI(Universal Discovery Description and Integration)来完成的。
4.1 SOAP
SOAP是Web Service的基本通信协议,是一种规范,用来定义SOAP消息的XML格式(XMLFormat)。包含在一对 SOAP 元素(SOAPElements)中的、结构正确的 XML 段就是 SOAP 消息。
SOAP 规范还介绍了如何将程序数据表示为 XML,以及如何使用 SOAP 进行远程过程调用 (RPC)。
最后SOAP规范还定义了HTTP消息是怎样传输SOAP消息的。这并不代表SOAP只能用HTTP来作为传输协议,MSMQ、SMTP、TCP/IP都可以做SOAP的传输协议。
4.2 WSDL
Web Services Description Language的缩写,是一个用来描述Web服务和说明如何与Web服务通信的XML语言。WSDL是Web Service的描述语言,用于描述Web Service的服务,接口绑定等,为用户提供详细的接口说明书。
举个例子,你要使用供应商的WebService构建应用程序。你可以向供应商索取使用Web Service的范例,然后按照范例来构建应用程序。这样可能出现意料不到的错误,比如说,你在程序中使用的客户代码的数据类型是integer,而供应商使用的数据类型是string。WSDL详细定义客户端消息的格式,需要什么样的参数,这样可以避免不必要的错误。
要查看 WSDL 的值,可以假设您要调用由您的一位业务伙伴提供的 SOAP 方法。您可以要求对方提供一些 SOAP 消息示例,然后编写您的应用程序以生成并使用与示例类似的消息。WSDL 通过明确的表示法指定请求消息必须包含的内容以及响应消息的样式。
WSDL 文件用于说明消息格式的表示法以 XML 架构标准为基础,这意味着它与编程语言无关,而且以标准为基础,因此适用于说明可从不同平台、以不同编程语言访问的 XML Web Service 接口。除说明消息内容外,WSDL 还定义了服务的位置,以及使用什么通信协议与服务进行通信。WSDL 文件定义了编写使用 XML Web Service 的程序所需的全部内容。
4.3 UDDI
UniversalDescription Discovery and Integration即统一描述、发现和集成协议。UDDI实现了一组可公开访问的接口,通过这些接口,网络服务可以向服务信息库注册其服务信息、服务需求者可以找到分散在世界各地的网络服务。
UDDI 目录条目是介绍所提供的业务和服务的 XML 文件。可以把它比喻成电话本,电话本里记录的是电话信息,而UDDI记录的是Web Service信息。你可以不把Web Service注册到UDDI。但如果要让全球的人知道你的Web Service,最好还是注册到UDDI。
UDDI 目录还包含若干种方法,可用于搜索构建您的应用程序所需的服务。例如,您可以搜索特定地理位置的服务提供商或者搜索特定的业务类型。之后,UDDI 目录将提供信息、联系方式、链接和技术数据,以便您确定能满足需要的服务。
UDDI 允许您查找提供所需的Web 服务的公司。如果您已经知道要与谁进行业务合作,但尚不了解它还能提供哪些服务,这时该如何处理呢?WS-Inspection规范允许您浏览特定服务器上提供的 XML Web Service 的集合,从中查找所需的服务。
UDDI 目录说明文件也是一个XML文档,它包括下面三个部分:
“白页(WhitePaper)”介绍提供Web Service的公司信息,比如名称、地址、联系方式等等;
“黄页(YellowPaper)” 说明UDDI目录的分类,包括基于标准分类法的行业类别,比如说金融、服务和印刷等等;
“绿页(greenPaper)” 详细介绍了访问服务的接口,以便用户能够编写应用程序以使用 Web 服务。
5.Web Service的开源实现
Web Service更多是一种标准,而不是一种具体的技术。不同的平台,不同的语言大都提供Web Service的开发实现,在JAVA领域,Web Service的框架很多,例如:Axis1&2,Xfire,CXF,java6自带Web Service引擎。
1)从JavaSE6.0开始,Java引入了对Web Service的原生支持。我们只需要简单的使用Java的Annotation标签即可将标准的Java方法发布成Web Service。但不是所有的Java类都可以发布成Web Service。Java类若要成为一个实现了Web Service的bean,它需要遵循下边这些原则:
Ø 这个类必须是public类
Ø 这些类不能是final的或者abstract
Ø 这个类必须有一个公共的默认构造函数
Ø 这个类绝对不能有finalize()方法
2)Axis2(Apache eXtensible Interaction System)是Apache下的一个重量级WebService框架,准确说它是一个Web Services / SOAP /WSDL 的引擎,是WebService框架的集大成者,它能不但能制作和发布WebService,而且可以生成Java和其他语言版WebService客户端和服务端代码。这是它的优势所在。但是,这也不可避免的导致了Axis2的复杂性,使用过的开发者都知道,它所依赖的包数量和大小都是很惊人的,打包部署发布都比较麻烦,不能很好的与现有应用整合为一体。但是如果你要开发Java之外别的语言客户端,Axis2提供的丰富工具将是你不二的选择。
3)XFire是一个高性能的WebService框架,在Java6之前,它的知名度甚至超过了Apache的Axis2,XFire的优点是开发方便,与现有的Web整合很好,可以融为一体,并且开发也很方便。但是对Java之外的语言,没有提供相关的代码工具。XFire后来被Apache收购了,原因是它太优秀了,收购后,随着Java6 JWS的兴起,开源的WebService引擎已经不再被看好,渐渐的都败落了。
4)Apache CXF是Apache旗下一个重磅的SOA简易框架,它实现了ESB(企业服务总线)。CXF 继承了 Celtix 和 XFire 两大开源项目的精华,不仅提供了对 JAX-WS 全面的支持,并且提供了多种Binding 、DataBinding、Transport 以及各种 Format 的支持,并且可以根据实际项目的需要,采用代码优先(Code First)或者 WSDL 优先(WSDL First)来轻松地实现 Web Services 的发布和使用。而且可以天然的和Spring进行无缝集成。Apache CXF已经是一个正式的Apache顶级项目。
6.基于 Apache CXF的Web Service开发
Web Service 支持不同语言开发,而不关心服务端或者客户端采用何种语言。这里讲解利用cxf进行Web Service开发。
这里先讲解Java中的web服务规范:
Java中共有三种WebService规范,分别是JAXM&SAAJ、JAX-WS(JAX-RPC)、JAX-RS。
JAX-WS(Java API ForXML-WebService),JDK1.6自带的版本为JAX-WS2.1,其底层支持为JAXB。早期的基于SOAP的JAVA的Web服务规范JAX-RPC(Java API For XML-RemoteProcedure Call)目前已经被JAX-WS规范取代,JAX-WS是JAX-RPC的演进版本,但JAX-WS并不完全向后兼容JAX-RPC,二者最大的区别就是RPC/encoded样式的WSDL,JAX-WS已经不提供这种支持。
这里的JAX-WS规范我们采用Apache CXF作为实现。
简单的说下注意事项:当你使用的是JDK1.5的时候,就必须要有jaxws-api-2.0.jar这个包的支持,如果使用的是JDK1.6就不用使用这个包了。因为1.6里已经有相关实现。
6.1 简单的CXF应用
6.1.1 服务端开发
1)首先下载CXF开发要用到的相关包,目前最新版本是apache-cxf-2.3.1。下载地址:
http://www.apache.org/dyn/closer.cgi?path=%2Fcxf%2F2.3.1%2Fapache-cxf-2.3.1.zip
下载解压后在apache-cxf-2.3.1目录下lib目录下,有所有要用到的jar包。
2)建一个web项目,将apache-cxf-2.1.3\lib目录下所有包添加到项目中。
注:这些包里面有jetty,cxf,spring的相关包,可以根据需要进行添加,如果不和spring进行整合,则不需要spring相关包。Jetty是一个类似于tomcat的web服务器,内置在cxf中,用于发布web服务。如果用jetty发布服务,则需要添加它的包,如果用tomcat则不需要。
3)写好一个接口和实现类(具体见demo中IHelloService和HelloServiceImpl),本demo中数据绑定方式采用cxf默认的jaxb方式,也可以采用aegis,其优点是不用jaxb中的注解方式。(基于SOAP的Web服务可用单个Java类的实现,但是最好是用“接口+实现”的方式来实现最佳。Web服务的接口称为SEI,即ServiceEndpoint Interface;而Web服务的实现称为SIB,即Service Implementation Bean。 SIB可以是一个POJO,也可以是无状态的会话EJB。)
4)发布服务
一种是通过CXF内置的Jetty应用服务器发布(见方法一,二),一种是通过tomcat发布(见方法三)。
Ø 方法一:使用SunJAX-WS 2中Endpoint.publish进行发布。(不需要其他配置与包)
Endpoint endpoint =
Endpoint.publish("http://localhost:8080/WSCXF/helloService",
new HelloServiceImpl());//这里是实现类
System.out.println("WS发布成功!");
Ø 方法二:用CXF的JaxWsServerFactoryBean类进行发布。(需要CXF相关包)
HelloServiceImpl impl = new HelloServiceImpl();
JaxWsServerFactoryBean factoryBean = newJaxWsServerFactoryBean();
factoryBean.setAddress("http://localhost:8080/WSCXF/helloService");
factoryBean.setServiceClass(IHelloService.class);//接口类
factoryBean.setServiceBean(impl);
factoryBean.create();
System.out.println("WS发布成功!");
方法一或者方法二都是发布到Jetty下。在main方法中运行方法一或者方法二代码,web服务就发布成功了。
Ø 方法三:利用cxf和spring整合在tomcat下进行发布。具体方法在后面的spring整合cxf时讲到。
Ø 方法四:重写loadBus方法。
书写一个类覆盖org.apache.cxf.transport.servlet.CXFNonSpringServlet的loadBus方法指定BUS以及发布你的web服务。
具体可查阅资料:
http://blog.csdn.net/zq9017197/archive/2010/12/26/6099684.aspx
查看web服务是否发布成功:
访问http://localhost:8080/WSCXF/helloService?wsdl 查看wsdl文件
6.1.2 客户端调用
客户端调用只需要服务端提供一个webservice的发布地址即可。不关心服务端发布方式等。
1)客户端代码生成
Ø 方法一:使用MyEclipse工具生成。
new-other-myeclipse-web service-web service client根据设置向导可以生成客户端,但最好使用CXF的wsdl2java来完成,因为CXF2.2+版本开始支持JAX-WS2.1规范,而MyEclipse自带的是xfire的一个插件,生成的客户端代码可能不是最新规范的。
Ø 方法二:通过wsdl2java的命令生成客户端代码。
先进入dos窗口,进入apache-cxf-2.3.1\bin所在目录,输入命令:
wsdl2java -pcom.jaxb.client -d e:/ http://127.0.0.1:8080/WSCXF/helloService?wsdl
命令格式为:wsdl2java –p 包名 –d 生成代码存放目录wsdl的url
其中的wsdl的url为要调用的webservice的服务地址
附加:wsdl2java用法:
wsdl2java -p com -d src -all aa.wsdl
-p 指定其wsdl的命名空间,也就是要生成代码的包名;
-d 指定要产生代码所在目录;
-client 生成客户端测试web service的代码;
-server 生成服务器启动web service的代码;
-impl 生成web service的实现代码;
-ant 生成build.xml文件;
-all 生成所有开始端点代码:types,serviceproxy, service interface, server mainline, client mainline, implementation object,and an Ant build.xml file。
详细用法见:http://cwiki.apache.org/CXF20DOC/wsdl-to-java.html
2)新建一个web客户端项目,将生成的客户端代码拷贝到src下。
3)调用web服务
Ø 方法一:使用标准的JAX-WS的API完成客户端调用(不需要导入任何CXF包)
//注意。此处http://service.jaxb.com/来源于wsdl文件中targetNamespace
QName qName =
newQName("http://service.jaxb.com/","HelloServiceImplService");
HelloServiceImplService helloServiceImplService =
new HelloServiceImplService(
new URL("http://localhost:8080/WSCXF/helloService?wsdl"),qName);
IHelloService helloService
=(IHelloService)helloServiceImplService.getPort(IHelloService.class);
Ø 方法二:使用CXF中JaxWsProxyFactoryBean客户端代理工厂调用web服务(需要导入CXF相关包)
JaxWsProxyFactoryBean soapFactoryBean = newJaxWsProxyFactoryBean();
soapFactoryBean.setAddress("http://localhost:8080/WSCXF/helloService");
soapFactoryBean.setServiceClass(IHelloService.class);
Object o = soapFactoryBean.create();
IHelloService helloService = (IHelloService)o;
Ø 方法三:
String endPointAddress = "http:// localhost:8080/WSCXF/helloService";
Service service = Service.create(
newQName("http://service.jaxb.com/","IHelloService"));
service.addPort(
new QName("http://service.jaxb.com/","IHelloServicePort");,
SOAPBinding.SOAP11HTTP_BINDING, endPointAddress);
IHelloService helloService =service.getPort(IHelloService.class);
Ø 方法四:(需要导入CXF相关包)
JaxWsDynamicClientFactory dcf =JaxWsDynamicClientFactory.newInstance();
org.apache.cxf.endpoint.Client client =
dcf.createClient("http://127.0.0.1:8080/WSCXF/helloService?wsdl");
//sayHello 为接口中定义的方法名称 张三为传递的参数 返回一个Object数组
Object[] objects=client.invoke("sayHello","张三");
//输出调用结果
System.out.println(objects[0].toString());
7.CXF和Spring整合
CXF可以很好的与Spring整合,然后发布在tomcat下,只需要简单的Spring配置即可。
7.1 服务端开发
1)新建web项目,并添加相关包。(包括spring和cxf相关包)
2)写好一个接口和实现类。(见demo)
3)新建beans.xml文件。
<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd">
<!--spring发布web服务配置 -->
<importresource="classpath:META-INF/cxf/cxf.xml" />
<importresource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<importresource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean id="helloService"class="com.jaxb.service.HelloServiceImpl" />
<!--
<bean id="helloService"class="com.aegis.service.HelloServiceImpl" />
-->
<!--endpoint 方式发布web服务和 server方式一样 -->
<!--
<jaxws:endpointid="helloServiceWs" address="/helloService"
implementor="#helloService"/>
-->
<!--
另一种写法,建议不要用这种方法 ,如果实现类有的属性要通过spring依赖注入的话,
这种方法只是简单的new个实现类,他的属性没有通过spring依赖注入给注入值
-->
<!--
<jaxws:endpointid="helloServiceWs" address="/helloService"
implementor="com.jaxb.service.HelloServiceImpl"/>
-->
<!—下面这个是wss4j的配置,后面会讲到相关知识,需要配置在spring配置文件中 -->
<!--wss4j 服务端配置 -->
<bean id="wss4jInInterceptor"
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entrykey="action" value="UsernameToken" />
<!--<entry key="passwordType" value="PasswordText" />-->
<!--密码使用MD5密文发送 -->
<entrykey="passwordDigest" value="PasswordText" />
<entrykey="passwordCallbackClass"
value="com.security.service.ServerPasswordCallbackHandler"/>
</map>
</constructor-arg>
</bean>
<jaxws:serverid="helloServiceWs" address="/helloService">
<jaxws:serviceBean>
<refbean="helloService" />
</jaxws:serviceBean><!--使用这种方法发布web服务 -->
<jaxws:inInterceptors>
<refbean="wss4jInInterceptor" />
</jaxws:inInterceptors><!—wss4j配置 -->
<!--<jaxws:serviceFactory>
<refbean="jaxWsServiceFactoryBean" />
</jaxws:serviceFactory> --><!—数据绑定方式配置 -->
</jaxws:server>
<!-- 通过Spring创建数据绑定的类-->
<!--<bean id="aegisBean"class="org.apache.cxf.aegis.databinding.AegisDatabinding" />
<bean id="jaxWsServiceFactoryBean"
class="org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean">
<property name="wrapped" value="true" />
<property name="dataBinding" ref="aegisBean"/>
</bean> -->
</beans>
4)配置web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!—在tomcat中发布需要配置servlet -->
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
5)发布web项目
因为在web.xml里面配置了servlet,则可以将项目发布到tomcat下,启动tomcat即可。
6)访问wsdl
http://localhost:8080/WSCXF/ws/helloService?wsdl
7.2 客户端调用
1)新建一个web客户端项目,用wsdl2java生成客户端代码。将生成的客户端代码拷贝到src下。添加spring相关包。
2)配置spring配置文件。
beans.xml存放在src目录下,具体配置如下:
<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd">
<!-- wss4j 配置在客户端,后面有讲到相关知识 -->
<!--wss4j 客户端配置 -->
<beanid="wss4jOutInterceptor"
class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entrykey="action" value="UsernameToken" />
<entrykey="user" value="Fetion" />
<!--<entry key="passwordType" value="PasswordText" />-->
<!--密码使用MD5密文发送 -->
<entrykey="passwordDigest" value="PasswordText" />
<entrykey="passwordCallbackClass"
value="com.security.client.ClientPasswordCallbackHandler"/>
</map>
</constructor-arg>
</bean>
<jaxws:client id="helloServeiceClient"
address="http://localhost:8080/WSCXF/ws/helloService"serviceClass="com.jaxb.client.IHelloService">
<jaxws:outInterceptors>
<refbean="wss4jOutInterceptor" />
</jaxws:outInterceptors><!--wss4j 客户端配置-->
</jaxws:client>
</beans>
2)调用web服务
在main方法中运行:
ClassPathXmlApplicationContext app = newClassPathXmlApplicationContext("beans.xml");//此处beans.xml放在src下,也需要放在其他目录下,但需要注明清楚
//获取webservice服务的操作接口
IHelloServicehelloService = (IHelloService)app.getBean("helloServeiceClient");
8.基于soap的web服务的WS-Security安全规范
8.1 传统的用户名令牌机制
Apache WSS4J(WebServiceSecurity For Java)实现了JAVA语言的WS-Security。WSS4J依赖于SAAJ。CXF中使用拦截器机制完成WSS4J功能的支持。只需要初始化WSS4JInInterceptor(对应还有一个WSS4JOutInterceptor)实例并添加相关信息即可。CXF2.1.x之后的版本可以完成SAAJInInterceptor(它也有对应的一个SAAJOutInterceptor)拦截器的自动注册,否则需要再注册一下SAAJ拦截器。
1)服务器端实现
在spring配置文件中配置:
<bean id="wss4jInInterceptor"
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entrykey="action" value="UsernameToken" />
<!--<entry key="passwordType" value="PasswordText" />-->
<!--密码使用MD5密文发送-->
<entrykey="passwordDigest" value="PasswordText" />
<entrykey="passwordCallbackClass"
value="com.security.service.ServerPasswordCallbackHandler"/>
</map>
</constructor-arg>
</bean>
<jaxws:server id="helloServiceWs" address="/helloService">
<jaxws:serviceBean>
<refbean="helloService" />
</jaxws:serviceBean><!--使用这种方法发布web服务 -->
<jaxws:inInterceptors>
<refbean="wss4jInInterceptor" />
</jaxws:inInterceptors>
<!--<jaxws:serviceFactory>
<refbean="jaxWsServiceFactoryBean" />
</jaxws:serviceFactory> -->
</jaxws:server>
这里我们使用构造方法参数初始化了一个输入的WSS4J拦截器。到底有哪些参数的键值对,可以再org.apache.ws.security.handler.WSHandlerConstants和
org.apache.ws.security.WSConstants中的产量列表中查找。例如:上面的第一组键值对action和UsernameToken都是WSHandlerConstants中的常量,表示验证机制是用户姓名令牌,也就是使用传统的用户名和密码机制。第二组的键值对分别是WSHandlerConstants和WSConstants中的常量,表示密码类型是文本,还可以是WSConstants.PASSWORD_DIGEST(密码会被加密为MD5)。第三组键值对的键表示服务器端验证密码的回调处理类,这个类必须实现JAVA安全认证框架中的
javax.security.auth.callback.CallbackHandler类,你也可以用passwordCallbackRef指向一个Bean的名字。
下面试服务器端具体密码回调处理类,它负责接收并处理客户端提交的用户名和密码,这个方法没有返回值,显示,如果验证失败,需要抛出异常进行表示。
package com.security.service;
import java.io.IOException;
import javax.security.auth.callback.Callback;
importjavax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSPasswordCallback;
importorg.apache.ws.security.WSSecurityException;
public class ServerPasswordCallbackHandlerimplements CallbackHandler {
public finalstatic String USER = "liu";
public finalstatic String PASSWORD = "lius";//设置用户名密码
public voidhandle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException{
WSPasswordCallbackwspassCallback = (WSPasswordCallback) callbacks[0];
System.out.println(wspassCallback.getIdentifier()+ "\t"
+wspassCallback.getPassword());
if(WSConstants.PASSWORD_TEXT.equals(wspassCallback.getPasswordType())){
if(wspassCallback.getIdentifier().equals(USER)
&&wspassCallback.getPassword().equals(PASSWORD)) {
System.out.println(".................验证通过!");
System.out.println(".................identifier= " + USER);
System.out.println(".................password= " + PASSWORD);
}else {
throw newWSSecurityException("............未通过验证!");
}
} else {
//密码使用MD5密文发送
System.out.println(wspassCallback.getIdentifier());
wspassCallback.setPassword(PASSWORD);
}
}
}
2)客户端实现
在spring配置文件中配置:(所以要使用wss4j,则需要和spring结合)
<bean id="wss4jOutInterceptor"
class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entrykey="action" value="UsernameToken" />
<entrykey="user" value="Fetion" />
<!--<entry key="passwordType" value="PasswordText" /> -->
<!--密码使用MD5密文发送-->
<entrykey="passwordDigest" value="PasswordText" />
<entrykey="passwordCallbackClass"
value="com.security.client.ClientPasswordCallbackHandler"/>
</map>
</constructor-arg>
</bean>
<jaxws:clientid="helloServeiceClient"
address="http://127.0.0.1:8080/WSCXF/helloService"serviceClass="com.jaxb.client.IHelloService">
<jaxws:outInterceptors>
<refbean="wss4jOutInterceptor" />
</jaxws:outInterceptors>
</jaxws:client>
可以看出,这里使用了WSS4J的输出拦截器,因为要将用户名和密码输出到服务端进行验证处理。另外与服务端不同的是多了一个user参数初始化WSS4J的输出拦截器,用于初始化用户名,这是一个必选项,稍后将在下面的客户端密码回调处理类进行重新设定值。
package com.security.client;
import java.io.IOException;
import javax.security.auth.callback.Callback;
importjavax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class ClientPasswordCallbackHandlerimplements CallbackHandler{
public finalstatic String USER = "liu";
public finalstatic String PASSWORD = "lius";//设置用户名密码
public voidhandle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException{
WSPasswordCallbackwspassCallback = (WSPasswordCallback)callbacks[0];
wspassCallback.setIdentifier(USER);
wspassCallback.setPassword(PASSWORD);
}
}
然后我们访问这个web服务,从控制台查看日志,可以看到在SOAP信封的Header中包装了<wsse:Security…等元素,元素包括了WS-Seurity的一些信息和设置的用户名和密码。如果使用MD5密文发送,则密码显示为null,只能显示用户名。
如果客户端和服务端的用户名或者密码不对应,则登录不上,会显示“............未通过验证!“。
8.2 数字签证方式
除了UsernameToken这种传统的安全机制,常用的验证动作还有一个就是使用数字签证技术,action的取值为Signature这需要你了解JAAS中相关内容,由于商业证书都是收费的,可以采用JDK自带的keytool命令自签名。具体实现请参考其他资料文档。
9.CXF数据绑定
CXF有多种数据绑定方式,如:AegisDatabinding,JAXB,MTOMAttachments。其中JAXB(JavaTMArchitecture for XML Binding)是其默认的数据绑定方式。
9.1 JAXB方式
JAXB是一套自动映射XML和Java实例的开发接口和工具。
如果web Service发布的接口为:
StringsayUserHello(User user);
List<User>findUsers();
且传入参数类型是类,而且返回的为List ,String 等,这样,发布webservice与普通java的没有区别,是因为JAXB都能支持。
但JAXB不能将一些 Java 类型自然映射到 XML 表示形式,例如,HashMap 或其他非 JavaBean 类。如参数类型为接口,以及Map,这样就要定义一个适配器使java类型适应自定义编组。一般有两步:
1)编写一个类继承XmlAdapter,以实现此抽象类的适配器。
2)安装使用注释XmlJavaTypeAdapter 的适配器。
具体实现见demo。
类XmlAdapter的说明:
类XmlAdapter<ValueType,BoundType>
BoundType - JAXB 不知道如何处理的一些类型。编写一个适配器,以便允许通过 ValueType 将此类型用作内存表示形式。
ValueType - JAXB 无需其他操作便知道如何处理的类型。
其两个抽象方法:
marshal(...):编组过程中,JAXB 绑定框架调用 XmlAdapter.marshal(..) 将 bound类型修改为 value 类型,然后将 value 类型编组为 XML 表示形式。
unmarshal(...):解组过程中,JAXB 绑定框架首先将 XML 表示形式解组为 value 类型,然后调用 XmlAdapter.unmarshal(..) 将 value 类型修改为 bound 类型。
常用的几个注释说明:
a. @XmlJavaTypeAdapter注释可以与下列编程元素一起使用: JavaBean 属性 、字段、参数 、包 、XmlJavaTypeAdapters 内部的元素。用来表示使用实现 XmlAdapter 的适配器,告诉其如何如何转换。
b. @XmlType 注释可以与以下程序元素一起使用:顶层类、枚举类型 。表示将类或枚举类型映射到 XML 模式类型。
c. @XmlAccessorType注释可以与以下程序元素一起使用:包、顶层类。表示控制默认情况下是否对字段或 Javabean 属性进行序列化。
@XmlAccessorType(XmlAccessType.FIELD)
FIELD :JAXB 绑定类中的每个非静态、非瞬态字段将会自动绑定到 XML,除非由 XmlTransient 注释。
d. @XmlElement 注释可以与以下程序元素一起使用: JavaBean 属性、非 static、非 transient 字段 、XmlElements 中的程序元素 。表示将 JavaBean 属性映射到派生于属性名称的 XML 元素。
9.2 Aegis Databinding方式
Aegis是一个快速,基于STAX(Streaming API for XML)的数据绑定,它能使采用代码优先方式发布webservice的情况更简单。Aegis 支持接口,集合类(包括Map)和各种数据类型。它不需要注释,也不需要写适配器(xmlAdapter)。