目录
1.发布web服务
2.发布Web服务使用Handler来增强Web服务的功能
3.建立安全的AXIS服务(上)
4.建立安全的AXIS服务(下)
5.在AXIS服务间传递JavaBean及其安全解决
AXIS学习笔记(一)
ronghao100 原创
前天头告诉我用SOAP WEB服务开发一个客户程序,用来与企业内部的ERP进行交互。晚上赶快找相关的资料猛看,总算对SOAP有了一定的认识。干程序员这行真不容易,好象得不停地学习新东西,一不小心就被淘汰:(不过学习也是个很有意思的事情。好了,废话少说,让我们开始吧。
一、软件环境
1、axis-1_2 (从apache网站下载最新axis-bin-1_2.zip解压即可)
2、Tomcat5.0
3、JDK5.0
二、相关配置
1、在你的%TOMCAT_HOME%\common\lib下需要加入三个包 activation.jar、mail.jar、tools.jar
2、环境变量设置
AXIS_HOME 即axis-bin-1_2.zip解压的目录(我的是在F:\soap\axis-1_2)
AXIS_LIB 即 %AXIS_HOME%\lib
AXISCLASSPATH 即 %AXIS_LIB%\axis.jar;%AXIS_LIB%\commons-discovery- 0.2.jar;%AXIS_LIB%\commons-logging-1.0.4.jar;%AXIS_LIB% \jaxrpc.jar;%AXIS_LIB%\saaj.jar;%AXIS_LIB%\log4j-1.2.8.jar;也就是把%AXIS_LIB%下所用JAR文件都导入
三、实验一下
在%AXIS_HOME%\webapps下找到axis文件夹,将其整个拷贝到%TOMCAT_HOME%\webapps下,启动
Tomcat,打开浏览器访问http://localhost:8080/axis/,出现以下页面说明你配置成功了。很简单吧:)
四、发布我们的第一个程序
第一个程序简单的返回HELLO WORLD!
HelloWorld.java public class HelloWorld { public String sayHello() { return "HELLO WORLD!"; } }
我们的第一种发布方式:
将HelloWorld.java拷贝到%TOMCAT_HOME%\webapps\axis下,然后将其改名为HelloWorld.jws,这样AXIS就自然将其发布了。现在写个客户端程序访问一下:
TestClient.java import org.apache.axis.client.Call; import org.apache.axis.client.Service; import javax.xml.rpc.ParameterMode; public class TestClient { public static void main(String [] args) throws Exception { String endpoint = "http://localhost:" +"8080"+ "/axis/HelloWorld.jws";//指明服务所在位置 Service service = new Service(); //创建一个Service实例,注意是必须的! Call call = (Call) service.createCall();//创建Call实例,也是必须的! call.setTargetEndpointAddress( new java.net.URL(endpoint) );//为Call设置服务的位置 call.setOperationName( "sayHello" );//注意方法名与HelloWorld.java中一样!! String res = (String) call.invoke( new Object[] {} );//返回String,没有传入参数 System.out.println( res ); } }
我的测试是在jbuilder2005中,注意项目中要导入其自带的AXIS包(当然应该把其中JAR文件替换一下),可以看到程序返回了 "HELLO WORLD!"
可以看到在AXIS里发布服务其实是一件很容易的事,这是因为这个服务很简单的原因:)下面我们介绍第二种发布方式,这是常用的。
我们的第二种发布方式:
1、将HelloWorld.java编译成HelloWorld.class,放到%TOMCAT_HOME%\webapps\axis\WEB-INF\classes
下
2、在%TOMCAT_HOME%\webapps\axis\WEB-INF下新建deploy.wsdd文件,即SOAP服务发布描述文件
deploy.wsdd <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="" target="_blank">http://xml.apache.org/axis/wsdd/providers/java"> <service name="HelloWorld" provider="java:RPC"> <parameter name="className" value="HelloWorld"/> <parameter name="allowedMethods" value="sayHello"/> </service> </deployment>
在DOS下转换目录到%TOMCAT_HOME%\webapps\axis\WEB-INF,命令:
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient deploy.wsdd
你会发现目录下多了一个server-config.wsdd文件,这就是AXIS的配置文件,以后所有的服务发布描述都会在里面找到。(当然,你可以直接修改它,不用再写deploy.wsdd)然后打开浏览器http://localhost:8080/axis/servlet/AxisServlet,你就会看到你的服务已发布
注意:可以直接写server-config.wsdd,如下:
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <handler type="java:org.apache.axis.handlers.http.URLMapper" name="URLMapper"/> <service name="myService" provider="java:RPC"> <parameter name="className" value="com.service.myService"/> <parameter name="allowedMethods" value="getusername"/> </service> <transport name="http"> <requestFlow> <handler type="URLMapper"/> </requestFlow> </transport> </deployment>
同样用客户端程序访问一下:(注意和上边的差别!!)
HelloClient.java import org.apache.axis.client.Call; import org.apache.axis.client.Service; public class HelloClient { public static void main(String [] args) throws Exception { String endpoint = "http://localhost:" +"8080"+ "/axis/services/HelloWorld";//注意!差别仅仅在这里!! Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(endpoint) ); call.setOperationName("sayHello" ); String res = (String) call.invoke( new Object[] {} ); System.out.println( res ); } }
好了,相信你对AXIS已有了大致的了解。接下来将会涉及到传参数、JAVABEAN对象,及AXIS的安全问题,下次再说吧:)也欢迎和我,一个快乐的JAVA程序员,联系:)ronghao100@hotmail.com
第三种方式:
1、编写服务端程序server,SayHello.java,编译server.SayHello.java
package server; public class SayHello { public String getName(String name) { return "hello "+name; } }
2、编写wsdd文件
deploy.wsdd文件内容如下:
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <service provider="java:RPC"> <parameter value="server.SayHello.getName"/> <parameter value="*"/> </service> </deployment>
3、发布服务:
编辑一个deploy.bat,Axis_Lib为axis.jar路径。内容如下:
set Axis_Lib=D:\workspace\test\WEB-INF\lib set Java_Cmd=java -Djava.ext.dirs=%Axis_Lib% set Axis_Servlet=http://localhost:8080/test/servlet/AxisServlet %Java_Cmd% org.apache.axis.client.AdminClient -l%Axis_Servlet% deploy.wsdd
执行这个批处理文件,这时候,如果提示成功的话,访问http://localhost:8080/test/services 就会显示服务列表。
4、生成客户端client stub文件
在浏览器上访问服务器端的服务,可以下载到WSDL文件,通过Axis的相关工具,可以自动从WSDL文件中生成Web Service的客户端代码。
编写一个WSDL2Java.bat文件,其内容如下:
set Axis_Lib=D:\workspace\test\WEB-INF\lib set Java_Cmd=java -Djava.ext.dirs=%Axis_Lib% set Output_Path=D:\workspace\test\src set Package=server.SayHello %Java_Cmd% org.apache.axis.wsdl.WSDL2Java -o%Output_Path% -p%Package% SayHello.wsdl
执行这个批处理文件就可以生成client stub.
生成的stub client文件列表为:
SayHello.java,SayHelloService.java,SayHelloServiceLocator.java,SayHelloSoapBindingStub.java .
5、编写客户端程序,编译并执行
下面是一段junit测试客户端代码。
import java.net.URL; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; public class TestWSClient extends TestCase { public TestWSClient(String string) { super(string); } public void SayHelloClient() throws Exception { SayHelloService service = new SayHelloServiceLocator(); SayHello_PortType client = service.getSayHello() ; String retValue = client.getName("clientname"); System.out.println(retValue); } public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(new TestWSClient("SayHelloClient")); return suite; } }
AXIS学习笔记(二)使用Handler来增强Web服务的功能
Handler的基本概念
J2EE Web 服务中的Handler技术特点非常像Servlet技术中的Filter。我们知道,在 Servlet中,当一个HTTP到达服务端时,往往要经过多个Filter对请求进行过滤,然后才到达提供服务的Servlet,这些Filter的功能往往是对请求进行统一编码,对用户进行认证,把用户的访问写入系统日志等。相应的,Web服务中的Handler通常也提供一下的功能:
对客户端进行认证、授权;
把用户的访问写入系统日志;
对请求的SOAP消息进行加密,解密;
为Web Services对象做缓存。
SOAP消息Handler能够访问代表RPC请求或者响应的SOAP消息。在JAX-RPC技术中,SOAP消息Handler可以部署在服务端,也可以在客户端使用。
下面我们来看一个典型的SOAP消息Handler处理顺序:
某个在线支付服务需要防止非授权的用户访问或者撰改服务端和客户端传输的信息,从而使用消息摘要(Message Digest)的方法对请求和响应的SOAP消息进行加密。当客户端发送SOAP消⑹保?突Ф说?andler把请求消息中的某些敏感的信息(如信用卡密码)进行加密,然后把加密后的SOAP消息传输到服务端;服务端的SOAP消息Handler截取客户端的请求,把请求的SOAP 消息进行解密,然后把解密后的SOAP消息派发到目标的Web服务端点。
Apache axis是我们当前开发Web服务的较好的选择,使用axisWeb服务开发工具,可以使用Handler来对服务端的请求和响应进行处理。典型的情况下,请求传递如图1所示。
图1 SOAP消息的传递顺序
在图中,轴心点(pivot point)是Apache与提供程序功能相当的部分,通过它来和目标的Web服务进行交互,它通常称为 Provider。axis中常用的Provider有Java:RPC,java:MSG,java:EJB。一个Web服务可以部署一个或者多个 Handler。
Apache axis中的Handler体系结构和JAX-RPC 1.0(JSR101)中的体系结构稍有不同,需要声明的是,本文的代码在axis中开发,故需要在axis环境下运行。
在axis环境下,SOAP消息Handler必须实现org.apache.axis.Handler接口(在JAX-RPC 1.0规范中,SOAP消息Handler必须实现javax.xml.rpc.handler.Handler接口),org.apache.axis.Handler接口的部分代码如下:
例程1 org.apache.axis.Handle的部分代码
public interface Handler extends Serializable { public void init(); public void cleanup(); public void invoke(MessageContext msgContext) throws AxisFault ; public void onFault(MessageContext msgContext); public void setOption(String name, Object value); public Object getOption(String name); public void setName(String name); public String getName(); public Element getDeploymentData(Document doc); public void generateWSDL(MessageContext msgContext) throws AxisFault; … }
为了提供开发的方便,在编写Handler时,只要继承org.apache.axis.handlers. BasicHandler即可,BasicHandler是Handler的一个模板,我们看它的部分代码:
例程2 BasicHandler的部分代码
public abstract class BasicHandler implements Handler { protected static Log log = LogFactory.getLog(BasicHandler.class.getName()); protected Hashtable options; protected String name; //这个方法必须在Handler中实现。 public abstract void invoke(MessageContext msgContext) throws AxisFault; public void setOption(String name, Object value) { if ( options == null ) initHashtable(); options.put( name, value ); } … }
BasicHandler中的(MessageContext msgContext)方法是Handler实现类必须实现的方法,它通过MessageContext来获得请求或者响应的SOAPMessage对象,然后对SOAPMessage进行处理。
在介绍Handler的开发之前,我们先来看一下目标Web服务的端点实现类的代码,如例程3所示。
例程3 目标Web服务的端点实现类
package com.hellking.webservice; public class HandleredService { //一个简单的Web服务 public String publicMethod(String name) { return "Hello!"+name; } } //另一个Web服务端点: package com.hellking.webservice; public class OrderService { //web服务方法:获得客户端的订单信息,并且对订单信息进行对应的处理, 通常情况是把订单的信息写入数据库,然后可客户端返回确认信息。 public String orderProduct(String name,String address,String item,int quantity,Card card) { String cardId=card.getCardId(); String cardType=card.getCardType(); String password=card.getPassword(); String rderInfo="name="+name+",address="+address+",item="+item+",quantity="+quantity+" ,cardId="+cardId+",cardType="+cardType+",password="+password; System.out.println("这里是客户端发送来的信息:"); System.out.println(orderInfo); return orderInfo; } }
下面我们分不同情况讨论Handler的使用实例。
使用Handler为系统做日志
Handler为系统做日志是一种比较常见而且简单的使用方式。和Servlet中的Filter一样,我们可以使用Handler来把用户的访问写入系统日志。下面我们来看日志Handler的具体代码,如例程4所示。
例程4 LogHandler的代码
package com.hellking.webservice; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.Date; import org.apache.axis.AxisFault; import org.apache.axis.Handler; import org.apache.axis.MessageContext; import org.apache.axis.handlers.BasicHandler; public class LogHandler extends BasicHandler { /**invoke,每一个handler都必须实现的方法。 */ public void invoke(MessageContext msgContext) throws AxisFault { //每当web服务被调用,都记录到log中。 try { Handler handler = msgContext.getService(); String filename = (String)getOption("filename"); if ((filename == null) || (filename.equals(""))) throw new AxisFault("Server.NoLogFile", "No log file configured for the LogHandler!", null, null); FileOutputStream fos = new FileOutputStream(filename, true); PrintWriter writer = new PrintWriter(fos); Integer counter = (Integer)handler.getOption("accesses"); if (counter == null) counter = new Integer(0); counter = new Integer(counter.intValue() + 1); Date date = new Date(); msgContext.getMessage().writeTo(System.out); String result = "在"+date + ": Web 服务 " + msgContext.getTargetService() + " 被调用,现在已经共调用了 " + counter + " 次."; handler.setOption("accesses", counter); writer.println(result); writer.close(); } catch (Exception e) { throw AxisFault.makeFault(e); } } }
前面我们说过,Handler实现类必须实现invoke方法,invoke方法是Handler处理其业务的入口点。LogHandler的主要功能是把客户端访问的Web服务的名称和访问时间、访问的次数记录到一个日志文件中。
下面部署这个前面开发的Web服务对像,然后为Web服务指定Handler。编辑Axis_Home/WEB-INF/ server-config.wsdd文件,在其中加入以下的内容:
<service name="HandleredService" provider="java:RPC"> <parameter name="allowedMethods" value="*"/> <parameter name="className" value="com.hellking.webservice.HandleredService"/> <parameter name="allowedRoles" value="chen"/> <beanMapping languageSpecificType="java:com.hellking.webservice.Card" qname="card:card" xmlns:card="card"/> <requestFlow> <handler name="logging" type="java:com.hellking.webservice.LogHandler"> <parameter name="filename" value="c:\\MyService.log"/> </handler> </requestFlow> </service> … </globalConfiguration> … <handler name="logging" type="java:com.hellking.webservice.LogHandler"> <parameter name="filename" value="c:\\MyService.log"/> </handler> … <service name="HandleredService" provider="java:RPC"> … <requestFlow> <handler type="logging"/> …<!--在这里可以指定多个Handler--> </requestFlow> </service>
http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen
注意:这个URL需要根据具体情况改变。
在Sun Jul 06 22:42:03 CST 2003: Web 服务 HandleredService 被调用,现在已经共调用了 1 次.
在Sun Jul 06 22:42:06 CST 2003: Web 服务 HandleredService 被调用,现在已经共调用了 2 次.
在Sun Jul 06 22:42:13 CST 2003: Web 服务 HandleredService 被调用,现在已经共调用了 3 次.
使用Handler对用户的访问认证
使用Handler为用户访问认证也是它的典型使用,通过它,可以减少在Web服务端代码中认证的麻烦,同时可以在部署描述符中灵活改变用户的访问权限。
对用户认证的Handler代码如下:
例程5 认证的Handler
package com.hellking.webservice; import…. //此handler的目的是对用户认证,只有认证的用户才能访问目标服务。 public class AuthenticationHandler extends BasicHandler { /**invoke,每一个handler都必须实现的方法。 */ public void invoke(MessageContext msgContext)throws AxisFault { SecurityProvider provider = (SecurityProvider)msgContext.getProperty("securityProvider"); if(provider==null) { provider= new SimpleSecurityProvider(); msgContext.setProperty("securityProvider", provider); } if(provider!=null) { String userId=msgContext.getUsername(); String password=msgContext.getPassword(); //对用户进行认证,如果authUser==null,表示没有通过认证, 抛出Server.Unauthenticated异常。 org.apache.axis.security.AuthenticatedUser authUser = provider.authenticate(msgContext); if(authUser==null) throw new AxisFault("Server.Unauthenticated", Messages.getMessage("cantAuth01", userId), null,null); //用户通过认证,把用户的设置成认证了的用户。 msgContext.setProperty("authenticatedUser", authUser); } } }
在AuthenticationHandler代码里,它从MessageContext中获得用户信息,然后进行认证,如果认证成功,那么就使用 msgContext.setProperty("authenticatedUser", authUser)方法把用户设置成认证了的用户,如果认证不成功,那么就抛出Server.Unauthenticated异常。
部署这个Handler,同样,在server-config里加入以下的内容:
<handler name="authen" type="java:com.hellking.webservice.AuthenticationHandler"/> … <service name="HandleredService" provider="java:RPC"> <parameter name="allowedRoles" value="chen"/> … </service>
WEB-INF/users.lst文件中加入以下用户:
hellking hellking
chen chen
http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen
将会提示输入用户名和密码,如图2所示。
图2 访问web服务时的验证
如果客户端是应用程序,那么可以这样在客户端设置用户名和密码:
例程6 在客户端设置用户名和密码
http://127.0.0.1:808 String endpointURL = "http://127.0.0.1:8080/handler/services/HandleredService?wsdl"; Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(endpointURL) ); call.setOperationName( new QName("HandleredService", "orderProduct") );//设置操作的名称。 //由于需要认证,故需要设置调用的用户名和密码。 call.getMessageContext().setUsername("chen"); call.getMessageContext().setPassword("chen");
使用Handler对用户的访问授权
对于已经认证了的用户,有时在他们操作某个特定的服务时,还需要进行授权,只有授权的用户才能继续进行操作。我们看对用户进行授权的Handler的代码。
例程7 对用户进行授权的代码
package com.hellking.webservice; import… //此handler的目的是对认证的用户授权,只有授权的用户才能访问目标服务。 public class AuthorizationHandler extends BasicHandler { /**invoke,每一个handler都必须实现的方法。 */ public void invoke(MessageContext msgContext) throws AxisFault { AuthenticatedUser user = (AuthenticatedUser)msgContext.getProperty("authenticatedUser"); if(user == null) throw new AxisFault("Server.NoUser", Messages.getMessage("needUser00"), null, null); String userId = user.getName(); Handler serviceHandler = msgContext.getService(); if(serviceHandler == null) throw new AxisFault(Messages.getMessage("needService00")); String serviceName = serviceHandler.getName(); String allowedRoles = (String)serviceHandler.getOption("allowedRoles"); if(allowedRoles == null) { return; } SecurityProvider provider = (SecurityProvider)msgContext.getProperty("securityProvider"); if(provider == null) throw new AxisFault(Messages.getMessage("noSecurity00")); for(StringTokenizer st = new StringTokenizer(allowedRoles, ","); st.hasMoreTokens();) { String thisRole = st.nextToken(); if(provider.userMatches(user, thisRole)) { return;//访问授权通过。 } } //没有通过授权,不能访问目标服务,抛出Server.Unauthorized异常。 throw new AxisFault("Server.Unauthorized", Messages.getMessage("cantAuth02", userId, serviceName), null, null); } }
在service-config.wsdd文件中,我们为Web服务指定了以下的用户:
<parameter name="allowedRoles" value="chen,hellking"/>
provider.userMatches(user, thisRole)将匹配允许访问Web服务的用户,如果匹配成功,那么授权通过,如果没有授权成功,那么抛出Server.Unauthorized异常。
使用Handler对SOAP消息进行加密、解密
由于SOAP消息在HTTP协议中传输,而HTTP协议的安全度是比较低的,怎么保证信息安全到达对方而不泄漏或中途被撰改,将是Web服务必须解决的问题。围绕Web服务的安全,有很多相关的技术,比如WS-Security,WS-Trace等,另外,还有以下相关技术:
XML Digital Signature(XML数字签名) XML Encryption (XML加密) XKMS (XML Key Management Specification) XACML (eXtensible Access Control Markup Language) SAML (Secure Assertion Markup Language) ebXML Message Service Security Identity Management & Liberty Project
不管使用什么技术,要使信息安全到达对方,必须把它进行加密,然后在对方收到信息后解密。为了提供开发的方便,可以使用Handler技术,在客户端发送信息前,使用客户端的Handler对SOAP消息中的关键信息进行加密;在服务端接收到消息后,有相应的Handler把消息进行解密,然后才把SOAP 消息派发到目标服务。
下面我们来看一个具体的例子。加入使用SOAP消息发送订单的信息,订单的信息如下:
例程8 要发送的订单SOAP消息
<soap-env:Envelope xmlns:soap-env="" target="_blank">http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header/> <soapenv:Body> <ns1:orderProduct soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encod ing/" xmlns:ns1="HandleredService"> <arg0 xsi:type="xsd:string">hellking</arg0> <arg1 xsi:type="xsd:string">beijing</arg1> <arg2 xsi:type="xsd:string">music-100</arg2> <arg3 xsi:type="xsd:int">10</arg3> <arg4 href="#id0"/> </ns1:orderProduct> <multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmls oap.org/soap/encoding/" xsi:type="ns2:card" xmlns:soapenc="http://schemas.xmlsoa p.org/soap/encoding/" xmlns:ns2="card"> <cardId xsi:type="xsd:string">234230572</cardId> <cardType xsi:type="xsd:string">visa</cardType> <password xsi:type="xsd:string">234kdsjf</password> </multiRef> </soapenv:Body> </soap-env:Envelope>
上面的黑体字是传输的敏感信息,故需要加密。我们可以使用Message Digest之类的方法进行加密。加密之后的信息结构如下:
例程9 把SOAP消息某些部分加密
<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope … <soapenv:Body> <ns1:orderProduct …> … <arg4 href="#id0"/> </ns1:orderProduct> <multiRef …> <ns3:EncryptedData xmlns:ns3="" target="_blank">http://www.w3.org/2000/11/temp-xmlenc"> <ns3:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ns3:DigestValue>rO0ABXQAkyA8Y2FyZ……. </ns3:DigestValue> </ns3:EncryptedData> </multiRef> </soapenv:Body> </soapenv:Envelope>
图3是使用Handler对SOAP消息进行加密、解密后,SOAP消息在传递过程中结构的改变。
图3 SOAP消息的加密和解密
从上图可以看出,通过使用加密、解密的Handler,可以确保消息的安全传递。进一步说,如果把这种Handler做成通用的组件,那么就可以灵活地部署到不同的服务端和客户端。
客户端的Handler的功能是把SOAP消息使用一定的规则加密,假如使用Message Digest加密方式,那么可以这样对敏感的信息加密:
例程10 对SOAP消息的敏感部分加密
SOAPElement ele= soapBodyElement.addChildElement(envelope.createName ("EncryptedData","","http://www.w3.org/2000/11/temp-xmlenc")); ele.addChildElement("DigestMethod").addAttribute(envelope.createName ("Algorithm"),"http://www.w3.org/2000/09/xmldsig#sha1"); byte[] digest=new byte[100]; ByteArrayOutputStream out=new ByteArrayOutputStream (100); MessageDigest md = MessageDigest.getInstance("SHA"); ObjectOutputStream oos = new ObjectOutputStream(out); //要加密的信息 String data = " <cardId xsi:type='xsd:string'>234230572 </cardId><cardType xsi:type='xsd:string'>visa</cardType> <password xsi:type='xsd:string'>234kdsjf</password>"; byte buf[] = data.getBytes(); md.update(buf); oos.writeObject(data); oos.writeObject(md.digest()); digest=out.toByteArray(); out.close(); ele.addChildElement("DigestValue").addTextNode(new sun.misc.BASE64Encoder().encode(digest));//对加密的信息编码
在客户端发送出SOAP消息时,客户端的Handler拦截发送的SOAP消息,然后对它们进行加密,最后把加密的信息传送到服务端。
服务端接收到加密的信息后,解密的Handler会把对应的加密信息解密。服务端Handler代码如例程11所示。
例程11 服务端解密Handler
package com.hellking.webservice; import… //此handler的目的是把加密的SOAP消息解密成目标服务可以使用的SOAP消息。 public class MessageDigestHandler extends BasicHandler { /**invoke,每一个handler都必须实现的方法。 */ public void invoke(MessageContext msgContext)throws AxisFault { try { //从messageContext例取得SOAPMessage对象。 SOAPMessage msg=msgContext.getMessage(); SOAPEnvelope env=msg.getSOAPPart().getEnvelope(); Iterator it=env.getBody().getChildElements(); SOAPElement multi=null; while(it.hasNext()) { multi=(SOAPElement)it.next();//multi是soapbody的最后一个child。 } String value="";//value表示加密后的值。 SOAPElement digestValue=null; Iterator it2=multi.getChildElements(); while(it2.hasNext()) { SOAPElement temp=(SOAPElement)it2.next(); Iterator it3=temp.getChildElements(env.createName("DigestValue", "ns3","http://www.w3.org/2000/11/temp-xmlenc")); if(it3.hasNext()) value=((SOAPElement)it3.next()).getValue();//获得加密的值 } //把加密的SOAPMessage解密成目标服务可以调用的SOAP消息。 SOAPMessage msg2=convertMessage(msg,this.decrypte(value)); msgContext.setMessage(msg2); } catch(Exception e) { e.printStackTrace(); } } //这个方法是把加密的数据进行解密,返回明文。 public String decrypte(String value) { String data=null; try { ByteArrayInputStream fis = new ByteArrayInputStream(new sun.misc.BASE64Decoder().decodeBuffer(value)); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); if (!(o instanceof String)) { System.out.println("Unexpected data in string"); System.exit(-1); } data = (String) o; System.out.println("解密后的值:" + data); o = ois.readObject(); if (!(o instanceof byte[])) { System.out.println("Unexpected data in string"); System.exit(-1); } byte origDigest[] = (byte []) o; MessageDigest md = MessageDigest.getInstance("SHA"); md.update(data.getBytes()); } … return data; } //把解密后的信息重新组装成服务端能够使用的SOAP消息。 public SOAPMessage convertMessage(SOAPMessage msg,String data) { …. } }
可以看出,服务端解密的Handler和客户端加密的Handler的操作是相反的过程。
总结
通过以上的讨论,相信大家已经掌握了Handler的基本使用技巧。可以看出,通过使用Handler,可以给Web服务提供一些额外的功能。在实际的开发中,我们可以开发出一些通用的Handler,然后通过不同的搭配方式把它们部署到不同的Web服务中。
+++++++++++++++++++++++++++++++++++++++
AXIS学习笔记(三)(建立安全的AXIS服务上)
ronghao100 原创
在前面的文章中,我们实现了最简单的AXIS服务。现在我们一起来讨论一下Web服务的安全问题。
根据应用的对安全要求的级别不同,可以采用不同的方式来实现安全性,以下是目前最常用的一些实现方式(从低到高排列):
1、J2EE Web应用默认的访问控制(数据是明文的);
2、使用axis的Handler进行访问控制(数据是明文的);
3、使用Servlet过滤器(Filter)进行访问控制(数据是明文的);
4、使用SSL/HTTPS协议来传输(加密的数据传输协议);
5、使用WS-Security规范对信息进行加密与身份认证(数据被加密传输)。
我们仅讨论第2、4、5种实现方式。在此之前我们先来了解一下AXIS自带的一个工具SOAPMonitor。
一、SOAPMonitor的使用
打开http://localhost:8080/axis/进入AXIS的主页面,你会看见:
SOAPMonitor-[disabled by default for security reasons] ,默认状态下其是不可用的,现在我们就来激活它。
1、到目录%TOMCAT_HOME%\webapps\axis下,你会找到SOAPMonitorApplet.java,在命令行中编译它:
javac -classpath %AXIS_HOME%\lib\axis.jar SOAPMonitorApplet.java
编译完之后你会看见目录下多了很多CLASS文件,它们的名字是SOAPMonitorApplet*.class
2、在目录%TOMCAT_HOME%\webapps\axis\WEB-INF下打开server-config.wsdd文件,将下面的两部分代码直
接加入其中相应的位置
第一部分:
<handler name="soapmonitor" type="java:org.apache.axis.handlers.SOAPMonitorHandler"> <parameter name="wsdlURL" value="/axis/SOAPMonitorService-impl.wsdl"/> <parameter name="namespace" value="" target="_blank"> http://tempuri.org/wsdl/2001/12/SOAPMonitorService-impl.wsdl"/> <parameter name="serviceName" value="SOAPMonitorService"/> <parameter name="portName" value="Demo"/> </handler>
第二部分:
<service name="SOAPMonitorService" provider="java:RPC"> <parameter name="allowedMethods" value="publishMessage"/> <parameter name="className" value="org.apache.axis.monitor.SOAPMonitorService"/> <parameter name="scope" value="Application"/> </service>
3、选择你要监控的服务
以上次的HelloWorld服务为例,在server-config.wsdd中你会找到这段代码
<service name="HelloWorld" provider="java:RPC"> <parameter name="allowedMethods" value="sayHello"/> <parameter name="className" value="HelloWorld"/> </service>
在这段代码中加入以下的代码:
<requestFlow> <handler type="soapmonitor"/> </requestFlow> <responseFlow> <handler type="soapmonitor"/> </responseFlow>
最后的样子是:
<service name="HelloWorld" provider="java:RPC"> <requestFlow> <handler type="soapmonitor"/> </requestFlow> <responseFlow> <handler type="soapmonitor"/> </responseFlow> <parameter name="allowedMethods" value="sayHello"/> <parameter name="className" value="HelloWorld"/> </service>
这样HelloWorld服务就被监控了
4、启动Tomcat,打开http://localhost:8080/axis/SOAPMonitor,你就会看到Applet界面,在
jbuilder2005中运行我们上次写的客户端程序 TestClient.java。OK!你会在Applet界面看
见客户端与服务器端互发的XML内容,注意这里是明文!
二、使用axis的Handler进行访问控制(对安全要求不高时推荐)
axis为Web服务的访问控制提供了相关的配置描述符,并且提供了一个访问控制的简单 Handler。默认情况下,你只要在配置描述符中添加用户,然后在Web服务器的部署描述符中自动允许的角色即可。
1、在axis的配置文件users.lst(位于WEB-INF目录下)中添加一个用户,如"ronghao1111",表示
用户名为ronghao,密码为1111。
2、把例HelloWorld的Web服务重新部署(新加的部分已标出)
<service name="HelloWorld" provider="java:RPC"> <requestFlow> <handler type="soapmonitor"/> <handler type="Authenticate"/> //新加的AXIS自带的Handler </requestFlow> <responseFlow> <handler type="soapmonitor"/> </responseFlow> <parameter name="allowedMethods" value="sayHello"/> <parameter name="allowedRoles" value="ronghao"/> //注意,这里是新加的部分! <parameter name="className" value="HelloWorld"/> </service>
在这个部署描述符中,指定HelloWorld服务只能被ronghao访问
3、修改客户端程序 TestClient.java,增加访问用户名、密码(新加的部分已标出)
TestClient.java
import org.apache.axis.client.Call; import org.apache.axis.client.Service; import javax.xml.rpc.ParameterMode; public class TestClient { public static void main(String [] args) throws Exception { String endpoint = "http://localhost:" +"8080"+ "/axis/HelloWorld"; Service service = new Service(); Call call = (Call) service.createCall(); call.getMessageContext().setUsername("ronghao");// 用户名。 call.getMessageContext().setPassword("1111");// 密码 call.setTargetEndpointAddress( new java.net.URL(endpoint) ); call.setOperationName( "sayHello" ); String res = (String) call.invoke( new Object[] {} ); System.out.println( res ); } }
执行TestClient,能够顺利访问Web服务;如果修改用户名或者密码,那么就不能访问 。同样,
你在http://localhost:8080/axis/SOAPMonitor中看到的请求和响应的XML是明文!
三、使用SSL/HTTPS协议来传输
Web服务也可以使用SSL作为传输协议。虽然JAX-RPC并没有强制规定是否使用SSL协议,但在tomcat
下使用HTTPS协议。
1、使用JDK自带的工具创建密匙库和信任库。
1)通过使用以下的命令来创建服务器端的密匙库:
keytool -genkey -alias Server -keystore server.keystore -keyalg RSA
输入keystore密码: changeit
您的名字与姓氏是什么?
[Unknown]: Server
您的组织单位名称是什么?
[Unknown]: ec
您的组织名称是什么?
[Unknown]: ec
您所在的城市或区域名称是什么?
[Unknown]: beijing
您所在的州或省份名称是什么?
[Unknown]: beijing
该单位的两字母国家代码是什么
[Unknown]: CN
CN=Server, OU=ec, O=ec, L=beijing, ST=beijing, C=CN 正确吗?
[否]: y
输入<Server>的主密码
(如果和 keystore 密码相同,按回车):
以上命令执行完成后,将获得一个名为server.keystore的密匙库。
2)生成客户端的信任库。首先输出RSA证书:
keytool -export -alias Server -file test_axis.cer -storepass changeit -keystore server.keystore
然后把RSA证书输入到一个新的信任库文件中。这个信任库被客户端使用,被用来验证服务器端的身份。
keytool -import -file test_axis.cer -storepass changeit -keystore client.truststore -alias serverkey -noprompt
以上命令执行完成后,将获得一个名为client.truststore的信任库。
3)同理生成客户端的密匙库client.keystore和服务器端的信任库server.truststore.方便起见给出.bat文件
gen-cer-store.bat内容如下:
set SERVER_DN="CN=Server, OU=ec, O=ec, L=BEIJINGC, S=BEIJING, C=CN" set CLIENT_DN="CN=Client, OU=ec, O=ec, L=BEIJING, S=BEIJING, C=CN" set KS_PASS=-storepass changeit set KEYINFO=-keyalg RSA keytool -genkey -alias Server -dname %SERVER_DN% %KS_PASS% -keystore server.keystore %KEYINFO% -keypass changeit keytool -export -alias Server -file test_axis.cer %KS_PASS% -keystore server.keystore keytool -import -file test_axis.cer %KS_PASS% -keystore client.truststore -alias serverkey -noprompt keytool -genkey -alias Client -dname %CLIENT_DN% %KS_PASS% -keystore client.keystore %KEYINFO% -keypass changeit keytool -export -alias Client -file test_axis.cer %KS_PASS% -keystore client.keystore keytool -import -file test_axis.cer %KS_PASS% -keystore server.truststore -alias clientkey -noprompt
好的,现在我们就有了四个文件:server.keystore,server.truststore,client.keystore,client.truststore
2、更改Tomcat的配置文件(server.xml),增加以下部署描述符:(其实里面有,只是被注释掉了)
<Connector port="8440" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" acceptCount="100" scheme="https" secure="true" clientAuth="true" keystoreFile="f:\server.keystore" keystorePass="changeit" truststoreFile="f:\server.truststore" truststorePass="changeit" sslProtocol="TLS" />
3、把HelloWorld重新部署一次,在server-config.wsdd中修改如下部署代码。(还原了而已)
<service name="HelloWorld" provider="java:RPC"> <requestFlow> <handler type="soapmonitor"/> </requestFlow> <responseFlow> <handler type="soapmonitor"/> </responseFlow> <parameter name="allowedMethods" value="sayHello"/> <parameter name="className" value="HelloWorld"/> </service>
4、修改客户端程序 TestClient.java(修改的部分已标出)
public class TestClient { public static void main(String [] args) throws Exception { String endpoint = "https://localhost:" +"8440"+ "/axis/HelloWorld";//注意区别在这里!https! Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(endpoint) ); call.setOperationName( "sayHello" ); String res = (String) call.invoke( new Object[] {} ); System.out.println( res ); } }
5、最后使用命令来执行客户端程序
java -cp %AXISCLASSPATH% -Djavax.net.ssl.keyStore=client.keystore -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.trustStore=client.truststore TestClient
+++++++++++++++++++++++++++++++++++++++
AXIS学习笔记(四)(建立安全的AXIS服务下)
ronghao100 原创
四、使用WS-Security规范对信息进行加密与身份认证
我们打算用Handler结合WSSecurity实现Web服务安全(Handler的有关内容请参阅AXIS学习笔记(二))
设想流程:用WSClientRequestHandler.java位于客户端对客户端发出的XML文档进行加密
WSServerRequestHandler.java位于服务器端对客户端发出的加密后的XML文档进行解密
WSServerResponseHandler.java位于服务器端对服务器端返回的XML文档进行加密
WSClientResponseHandler.java位于客户端对服务器端返回的XML文档进行解密
1、使用ISNetworks安全提供者,ISNetworks实现了RSA加密、解密算法。
当然,你也可以使用其它的安全提供者,并且可以使用不同的加密算法。
ISNetworks相关包ISNetworksProvider.jar。拷贝到%TOMCAT_HOME% \webapps\axis\WEB-INF\lib
2、Trust Services Integration Kit提供了一个WS-Security实现。你可以从http://www.xmltrustcenter.org获得相关库文件,分别是ws-security.jar和tsik.jar。ws-security.jar中包含一个WSSecurity类,我们使用它来对XML进行数字签名和验证,加密与解密。同样拷贝到%TOMCAT_HOME%\webapps\axis\WEB-INF\lib
3、创建密匙库和信任库。(见上文,一模一样!)
4、框架结构
WSClientHandler.java //基类,包含了一些公用方法
WSClientRequestHandler.java //继承于WSClientHandler.java,调用WSHelper.java对客户端发出的XML文档进行加密
WSClientResponseHandler.java //继承于WSClientHandler.java,调用WSHelper.java对服务器端返回的XML文档进行解密
WSServerHandler.java //基类,包含了一些公用方法
WSServerRequestHandler.java //继承于WSServerHandler.java,调用WSHelper.java对客户端发出的加密后的XML文档进行解密
WSServerResponseHandler.java//继承于WSServerHandler.java,调用WSHelper.java对服务器端返回的XML文档进行加密
WSHelper.java //核心类,对SOAP消息签名、加密、解密、身份验证
MessageConverter.java //帮助类,Document、SOAP消息互相转换
5、具体分析(在此强烈建议看一下tsik.jar的API)
WSHelper.java public class WSHelper { static String PROVIDER="ISNetworks";//JSSE安全提供者。 //添加JSSE安全提供者,你也可以使用其它安全提供者。只要支持DESede算法。这是程序里动态加载还可以在JDK中静态加载 static { java.security.Security.addProvider(new com.isnetworks.provider.jce.ISNetworksProvider()); } /** *对XML文档进行数字签名。 */ public static void sign(Document doc, String keystore, String storetype, String storepass, String alias, String keypass) throws Exception { FileInputStream fileInputStream = new FileInputStream(keystore); java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype); keyStore.load(fileInputStream, storepass.toCharArray()); PrivateKey key = (PrivateKey)keyStore.getKey(alias, keypass.toCharArray()); X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias); SigningKey sk = SigningKeyFactory.makeSigningKey(key); KeyInfo ki = new KeyInfo(); ki.setCertificate(cert); WSSecurity wSSecurity = new WSSecurity();//ws-security.jar中包含的WSSecurity类 wSSecurity.sign(doc, sk, ki);//签名。 } /** *对XML文档进行身份验证。 */ public static boolean verify(Document doc, String keystore, String storetype, String storepass) throws Exception { FileInputStream fileInputStream = new FileInputStream(keystore); java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype); keyStore.load(fileInputStream, storepass.toCharArray()); TrustVerifier verifier = new X509TrustVerifier(keyStore); WSSecurity wSSecurity = new WSSecurity(); MessageValidity[] resa = wSSecurity.verify(doc, verifier, null,null); if (resa.length > 0) return resa[0].isValid(); return false; } /** *对XML文档进行加密。必须有JSSE提供者才能加密。 */ public static void encrypt(Document doc, String keystore, String storetype, String storepass, String alias) throws Exception { try { FileInputStream fileInputStream = new FileInputStream(keystore); java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype); keyStore.load(fileInputStream, storepass.toCharArray()); X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias); PublicKey pubk = cert.getPublicKey(); KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede",PROVIDER); keyGenerator.init(168, new SecureRandom()); SecretKey key = keyGenerator.generateKey(); KeyInfo ki = new KeyInfo(); ki.setCertificate(cert); WSSecurity wSSecurity = new WSSecurity(); //加密。 wSSecurity.encrypt(doc, key, AlgorithmType.TRIPLEDES, pubk, AlgorithmType.RSA1_5, ki); } catch(Exception e) { e.printStackTrace(); } } /** *对文档进行解密。 */ public static void decrypt(Document doc, String keystore, String storetype, String storepass, String alias, String keypass) throws Exception { FileInputStream fileInputStream = new FileInputStream(keystore); java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype); keyStore.load(fileInputStream, storepass.toCharArray()); PrivateKey prvk2 = (PrivateKey)keyStore.getKey(alias, keypass.toCharArray()); WSSecurity wSSecurity = new WSSecurity(); //解密。 wSSecurity.decrypt(doc, prvk2, null); WsUtils.removeEncryptedKey(doc);//从 WS-Security Header中删除 EncryptedKey 元素 } public static void removeWSSElements(Document doc) throws Exception { WsUtils.removeWSSElements(doc);// 删除WSS相关的元素。 } } WSClientHandler.java //继承自org.apache.axis.handlers.BasicHandler即AXIS内在的 public class WSClientHandler extends BasicHandler{ protected String keyStoreFile ; protected String keyStoreType ="JKS";//默认 protected String keyStorePassword ; protected String keyAlias ; protected String keyEntryPassword ; protected String trustStoreFile ; protected String trustStoreType = "JKS";//默认 protected String trustStorePassword ; protected String certAlias ; public void setInitialization(String keyStoreFile,String keyStoreType,String keyStorePassword, String keyAlias,String keyEntryPassword,String trustStoreFile, String trustStoreType,String trustStorePassword,String certAlias){ this.keyStoreFile=keyStoreFile; this.keyStoreType=keyStoreType; this.keyStorePassword=keyStorePassword; this.keyAlias=keyAlias; this.keyEntryPassword=keyEntryPassword; this.trustStoreFile=trustStoreFile; this.trustStoreType=trustStoreType; this.trustStorePassword=trustStorePassword; this.certAlias=certAlias; } public void setInitialization(String keyStoreFile,String keyStorePassword, String keyAlias,String keyEntryPassword,String trustStoreFile, String trustStorePassword,String certAlias){ this.keyStoreFile=keyStoreFile; this.keyStorePassword=keyStorePassword; this.keyAlias=keyAlias; this.keyEntryPassword=keyEntryPassword; this.trustStoreFile=trustStoreFile; this.trustStorePassword=trustStorePassword; this.certAlias=certAlias; } public void invoke(MessageContext messageContext) throws AxisFault {//在这个方法里对XML文档进行处理 //do nothing now! } public void onFault(MessageContext msgContext) { System.out.println("处理错误,这里忽略!"); } } WSClientRequestHandler.java public class WSClientRequestHandler extends WSClientHandler{ public void invoke(MessageContext messageContext) throws AxisFault { try { SOAPMessage soapMessage = messageContext.getMessage(); Document doc = MessageConverter.convertSoapMessageToDocument(soapMessage); //soapMessage转换为Document WSHelper.sign(doc, keyStoreFile, keyStoreType,keyStorePassword, keyAlias, keyEntryPassword); //数字签名 WSHelper.encrypt(doc, trustStoreFile, trustStoreType, trustStorePassword, certAlias); //加密 soapMessage = MessageConverter.convertDocumentToSOAPMessage(doc); //处理后的Document再转换回soapMessage messageContext.setMessage(soapMessage); } catch (Exception e){ System.err.println("在处理响应时发生以下错误: " + e); e.printStackTrace(); } } } WSClientResponseHandler.java public class WSClientResponseHandler extends WSClientHandler{ public void invoke(MessageContext messageContext) throws AxisFault { try { SOAPMessage soapMessage = messageContext.getCurrentMessage(); Document doc = MessageConverter.convertSoapMessageToDocument(soapMessage); WSHelper.decrypt(doc, keyStoreFile, keyStoreType, keyStorePassword, keyAlias, keyEntryPassword);//解密 WSHelper.verify(doc, trustStoreFile, trustStoreType, trustStorePassword);//验证 WSHelper.removeWSSElements(doc); soapMessage = MessageConverter.convertDocumentToSOAPMessage(doc); messageContext.setMessage(soapMessage); } catch (Exception e){ e.printStackTrace(); System.err.println("在处理响应时发生以下错误: " + e); } } } WSServerHandler.java public class WSServerHandler extends BasicHandler{ protected String keyStoreFile ; protected String keyStoreType ="JKS";//默认 protected String keyStorePassword ; protected String keyAlias ; protected String keyEntryPassword ; protected String trustStoreFile ; protected String trustStoreType = "JKS";//默认 protected String trustStorePassword ; protected String certAlias ; public void invoke(MessageContext messageContext) throws AxisFault { //do nothing now! } public void onFault(MessageContext msgContext) { System.out.println("处理错误,这里忽略!"); } public void init() { //初始化,从配置文件server-config.wsdd中读取属性 keyStoreFile = (String)getOption("keyStoreFile"); if(( keyStoreFile== null) ) System.err.println("Please keyStoreFile configured for the Handler!"); trustStoreFile = (String)getOption("trustStoreFile"); if(( trustStoreFile== null) ) System.err.println("Please trustStoreFile configured for the Handler!"); keyStorePassword = (String)getOption("keyStorePassword"); if(( keyStorePassword== null) ) System.err.println("Please keyStorePassword configured for the Handler!"); keyAlias = (String)getOption("keyAlias"); if(( keyAlias== null) ) System.err.println("Please keyAlias configured for the Handler!"); keyEntryPassword = (String)getOption("keyEntryPassword"); if(( keyEntryPassword== null) ) System.err.println("Please keyEntryPassword configured for the Handler!"); trustStorePassword = (String)getOption("trustStorePassword"); if(( trustStorePassword== null) ) System.err.println("Please trustStorePassword configured for the Handler!"); certAlias = (String)getOption("certAlias"); if ((certAlias==null)) System.err.println("Please certAlias configured for the Handler!"); if ((getOption("keyStoreType")) != null) keyStoreType = (String)getOption("keyStoreType"); if ((getOption("trustStoreType")) != null) trustStoreType = (String)getOption("trustStoreType"); } } WSServerRequestHandler.java public class WSServerRequestHandler extends WSServerHandler{ public void invoke(MessageContext messageContext) throws AxisFault { try { SOAPMessage msg = messageContext.getCurrentMessage(); Document doc = MessageConverter.convertSoapMessageToDocument(msg); System.out.println("接收的原始消息:"); msg.writeTo(System.out); WSHelper.decrypt(doc, keyStoreFile, keyStoreType, keyStorePassword, keyAlias, keyEntryPassword);//解密 WSHelper.verify(doc, trustStoreFile, trustStoreType, trustStorePassword);//验证 WSHelper.removeWSSElements(doc); msg = MessageConverter.convertDocumentToSOAPMessage(doc); System.out.println("怀原后的原始消息:"); msg.writeTo(System.out); messageContext.setMessage(msg); } catch (Exception e){ e.printStackTrace(); System.err.println("在处理响应时发生以下错误: " + e); } } } WSServerResponseHandler.java public class WSServerResponseHandler extends WSServerHandler{ public void invoke(MessageContext messageContext) throws AxisFault { try { SOAPMessage soapMessage = messageContext.getMessage(); System.out.println("返回的原始消息:"); soapMessage.writeTo(System.out); Document doc = MessageConverter.convertSoapMessageToDocument(soapMessage); WSHelper.sign(doc, keyStoreFile, keyStoreType, keyStorePassword, keyAlias, keyEntryPassword);//数字签名 WSHelper.encrypt(doc, trustStoreFile, trustStoreType,//加密 trustStorePassword, certAlias); soapMessage = MessageConverter.convertDocumentToSOAPMessage(doc); System.out.println("返回的加密后的消息:"); soapMessage.writeTo(System.out); messageContext.setMessage(soapMessage); } catch (Exception e){ System.err.println("在处理响应时发生以下错误: " + e); e.printStackTrace(); } } }
6、应用
为方便使用,把上述文件打包为ws-axis.jar,放入%TOMCAT_HOME%\webapps\axis\WEB-INF\lib
1)把HelloWorld重新部署一次,在server-config.wsdd中修改如下部署代码。
<service name="HelloWorld" provider="java:RPC"> <parameter name="allowedMethods" value="*"/> <parameter name="className" value="HelloWorld"/> <requestFlow> <handler type="soapmonitor"/> <handler type="java:com.ronghao.WSAxis.WSServerRequestHandler"> <parameter name="keyStoreFile" value="f:\server.keystore"/> <parameter name="trustStoreFile" value="f:\server.truststore"/> <parameter name="keyStorePassword" value="changeit"/> <parameter name="keyAlias" value="Server"/> <parameter name="keyEntryPassword" value="changeit"/> <parameter name="trustStorePassword" value="changeit"/> <parameter name="certAlias" value="clientkey"/> </handler> </requestFlow> <responseFlow> <handler type="soapmonitor"/> <handler type="java:com.ronghao.WSAxis.WSServerResponseHandler"> <parameter name="keyStoreFile" value="f:\server.keystore"/> <parameter name="trustStoreFile" value="f:\server.truststore"/> <parameter name="keyStorePassword" value="changeit"/> <parameter name="keyAlias" value="Server"/> <parameter name="keyEntryPassword" value="changeit"/> <parameter name="trustStorePassword" value="changeit"/> <parameter name="certAlias" value="clientkey"/> </handler> </responseFlow> </service> 2)修改客户端程序 TestClient.java(修改的部分已标出,记着导入ws-axis.jar) import javax.xml.namespace.QName; import org.apache.axis.client.Call; import org.apache.axis.client.Service; import com.ronghao.WSAxis.*; public class WSSClient1 { public static void main(String [] args) { try { //服务端的url,需要根据情况更改。 String endpointURL = "http://localhost:8080/axis/services/HelloWorld"; Service svc = new Service(); WSClientHandler handler=new WSClientRequestHandler(); //注意新加的HANDLER handler.setInitialization("f:/client.keystore","changeit","Client","changeit", "f:/client.truststore","changeit","serverkey");//初始化 WSClientHandler handlee=new WSClientResponseHandler(); //注意新加的HANDLER handlee.setInitialization("f:/client.keystore","changeit","Client","changeit", "f:/client.truststore","changeit","serverkey");//初始化 Call call =(Call)svc.createCall(); call.setClientHandlers(handler,handlee);//添加Handler call.setTargetEndpointAddress(new java.net.URL(endpointURL)); call.setOperationName(new QName("sayHello")); String result = (String) call.invoke( new Object [] {}); System.out.println("the result"+result); } catch (Exception e) { e.printStackTrace(); } } }
运行的时候http://localhost:8080/axis/SOAPMonitor中看到的请求的XML就已加密!
总结
这里对代码的解释是不够的,很多概念没有提到。建议你最好看tsik.jar和AXIS的API深入了解。另外对ws-axis.jar的加解密实现打算运用apache的wss4j,相关网址http://ws.apache.org/ws-fx/wss4j/。不过这个东西也应该够用了暂时。所有的源文件在附件中附件:soapTest.zip(279K)
++++++++++++++++++++++++++++++++++++++
AXIS学习笔记(五)( 在AXIS服务间传递JavaBean及其安全解决)
ronghao100 原创
在AXIS服务间传递JavaBean及其安全解决
这是AXIS学习笔记的最后一篇。在前面我们讨论了最简单的HelloWorld服务,客户端并没有向服务器端
传递参数,现在我们来传传JavaBean。当然,也可以传递你自己定义的JAVA类,但那样你必须自己创建
专门的XML序列化器和反序列化器;而对JavaBean,AXIS提供了现成的序列化器。(有人说:懒惰是程序员最大的美德,我喜欢,所以我就传传JavaBean)
一、服务器端
1、CLASS类两个Order.class,OrderTest.class,位于%TOMCAT_HOME%\webapps\axis\WEB-INF\classes下
这两个类都直接给出源码,不再说明
Order.java public class Order { private String id; private String name; public void setId(String id){ this.id=id; } public String getId(){ return id; } public void setName(String name){ this.name=name; } public String getName(){ return name; } } OrderTest.java public class OrderTest { public Order returnOrder(Order order){ Order newOrder=new Order(); if(order.getId().equals("1")) newOrder.setName("ronghao"); else newOrder.setName("haorong"); return newOrder; } } 2、修改服务器端配置文件server-config.wsdd 在server-config.wsdd中相应位置添加以下代码 <service name="Order" provider="java:RPC"> <parameter name="allowedMethods" value="returnOrder"/> <parameter name="className" value="OrderTest"/> <beanMapping languageSpecificType="java:Order" qname="ns1:Order" xmlns:ns1="urn:BeanService"/> </service>
可以看到和前面的发布服务代码相比仅多了一行代码
<beanMapping languageSpecificType="java:Order" qname="ns1:Order" xmlns:ns1="urn:BeanService"/> languageSpecificType属性指定JavaBean类文件位置,例如: languageSpecificType="java:com.ronghao.axis.Order" qname属性指定JavaBean类的名字
其他是固定的。
二、客户端
客户端类文件一个OrderClient.class,代码如下(变化的部分加注释):
public class OrderClient { public static void main(String args[]) throws Exception { String endpoint = "http://localhost:8080/axis/services/Order"; //服务所在位置 Order order=new Order(); //JavaBean order.setId("1"); Service service = new Service(); Call call = (Call)service.createCall(); //注册JavaBean,注意和server-config.wsdd中的配置代码比较 QName qn = new QName("urn:BeanService", "Order"); call.registerTypeMapping(Order.class, qn, new BeanSerializerFactory(Order.class, qn), new BeanDeserializerFactory(Order.class, qn)); String name="no!"; try { call.setTargetEndpointAddress(new URL(endpoint)); //调用的服务器端方法 call.setOperationName(new QName("Order", "returnOrder")); //设定传入的参数,这里qn即Order.class call.addParameter("arg1", qn, ParameterMode.IN); //设定返回的参数是Order.class call.setReturnType(qn, Order.class); Order result = (Order)call.invoke(new Object[] { order }); if(result != null) name = result.getName(); } catch(Exception e) { System.err.println(e); } System.out.println(name); } }
OK!运行一下,就可以看到返回了"ronghao"。
和上一篇文章一样,我们不容许在网络中传递XML是明文,于是需要加密和验证。这里我们继续采用上次所讲的框架。(已打包成ws-axis.jar)
一、修改服务器端配置文件server-config.wsdd(和上一文章一模一样!不再罗嗦)
在server-config.wsdd中相应位置添加以下代码
<requestFlow> <handler type="soapmonitor"/> <handler type="java:com.ronghao.WSAxis.WSServerRequestHandler"> <parameter name="keyStoreFile" value="f:\server.keystore"/> <parameter name="trustStoreFile" value="f:\server.truststore"/> <parameter name="keyStorePassword" value="changeit"/> <parameter name="keyAlias" value="Server"/> <parameter name="keyEntryPassword" value="changeit"/> <parameter name="trustStorePassword" value="changeit"/> <parameter name="certAlias" value="clientkey"/> </handler> </requestFlow> <responseFlow> <handler type="soapmonitor"/> <handler type="java:com.ronghao.WSAxis.WSServerResponseHandler"> <parameter name="keyStoreFile" value="f:\server.keystore"/> <parameter name="trustStoreFile" value="f:\server.truststore"/> <parameter name="keyStorePassword" value="changeit"/> <parameter name="keyAlias" value="Server"/> <parameter name="keyEntryPassword" value="changeit"/> <parameter name="trustStorePassword" value="changeit"/> <parameter name="certAlias" value="clientkey"/> </handler> </responseFlow>
二、客户端(区别就在这里,注意!!)
首先在这里要说一下,客户端代码编写困扰了我很长一段时间(整整一天),因为它并不象我想象的那么简单,当然解决起来还是挺简单的:)问题的解决经历了三个阶段
第一阶段:
在这个阶段我想当然的在OrderClient.class中加入了如下代码:
WSClientHandler handler=new WSClientRequestHandler();//注意新加的HANDLER handler.setInitialization("f:/client.keystore","changeit","Client","changeit", "f:/client.truststore","changeit","serverkey");//初始化 WSClientHandler handlee=new WSClientResponseHandler();//注意新加的HANDLER handlee.setInitialization("f:/client.keystore","changeit","Client","changeit", "f:/client.truststore","changeit","serverkey");//初始化 call.setClientHandlers(handler,handlee);//添加Handler
这个方法也是我在上一文章里介绍的,结果抛出以下异常:
faultString: org.xml.sax.SAXException: Deserializing parameter 'newProfileReturn': could not find deserializer for type {urn:BeanService Order}SerializableProfile
也就是说不能正常解析XML文件,于是理所当然的郁闷了,觉得代码中肯定漏设了CALL的一个属性,于是查看AXIS的源代码,没有结果!转机出现在下面一行代码,在不断的抛出异常中我修改了代码
将call.setClientHandlers(handler,handlee);改为
call.setClientHandlers(null,null);
结果程序还是抛出同样的异常,于是意识到这可能是AXIS的一个BUG,为证明这一点,我将下面的Handler初始化代码删除
WSClientHandler handler=new WSClientRequestHandler();//注意新加的HANDLER handler.setInitialization("f:/client.keystore","changeit","Client","changeit", "f:/client.truststore","changeit","serverkey");//初始化 WSClientHandler handlee=new WSClientResponseHandler();//注意新加的HANDLER handlee.setInitialization("f:/client.keystore","changeit","Client","changeit", "f:/client.truststore","changeit","serverkey");//初始化
结果还是抛出同样的异常,果然是BUG!得到这个结论后去了apache AXIS主页,在问题列表中见到了完全一样问题的提交,但没有解答(晕!)
最后得到了结论:call的setClientHandlers()方法只有当call处理简单的数据类型,如String,int等等才能正常使用!
(当然,如果你对这个问题有不同的见解,欢迎和我联系。或许我错了,但程序不运行是真的:))
第二阶段:
开始在google上找问题的解决方法,这也是我的习惯:)。找了一个类似问题的讨论,地址如下:
http://marc.theaimsgroup.com/?l=axis-user&m=111259980822735&w=2
他们的解决方法是Handler继承于javax.xml.rpc.handler.Handler,然后在程序里动态注册而在我的ws-axis.jar里Handler继承于org.apache.axis.handlers.BasicHandler。当然,
javax.xml.rpc.handler.Handler 是org.apache.axis.handlers.BasicHandler的老爸,但在程序里老爸和儿子之间却不能很好的兼容,这也许就是所谓的代沟??无奈中重新写了Handler,但在运行中却抛出异常,提示message在被invoke的时候已被更改。我靠,Handler的作用就是来更改 message的啊!这是什么世道!
我知道很多程序采用的就是这种方法,但我好象怎么修改都抛出上述异常。
第三阶段
既然在程序里动态注册Handler行不通,于是决定写个单独的配置文件来注册Handler。如果这种方法不幸失败就返回第二阶段。好马为什么不吃回头草??
1、ws-axis.jar中修改WSClientHandler.class,修改后如下,我想你一看就明白为何修改
public class WSClientHandler extends BasicHandler{ protected String keyStoreFile ; protected String keyStoreType ="JKS"; protected String keyStorePassword ; protected String keyAlias ; protected String keyEntryPassword ; protected String trustStoreFile ; protected String trustStoreType = "JKS"; protected String trustStorePassword ; protected String certAlias ; public void init() { keyStoreFile = (String)getOption("keyStoreFile"); if(( keyStoreFile== null) ) System.err.println("Please keyStoreFile configured for the Handler!"); trustStoreFile = (String)getOption("trustStoreFile"); if(( trustStoreFile== null) ) System.err.println("Please trustStoreFile configured for the Handler!"); keyStorePassword = (String)getOption("keyStorePassword"); if(( keyStorePassword== null) ) System.err.println("Please keyStorePassword configured for the Handler!"); keyAlias = (String)getOption("keyAlias"); if(( keyAlias== null) ) System.err.println("Please keyAlias configured for the Handler!"); keyEntryPassword = (String)getOption("keyEntryPassword"); if(( keyEntryPassword== null) ) System.err.println("Please keyEntryPassword configured for the Handler!"); trustStorePassword = (String)getOption("trustStorePassword"); if(( trustStorePassword== null) ) System.err.println("Please trustStorePassword configured for the Handler!"); certAlias = (String)getOption("certAlias"); if ((certAlias==null)) System.err.println("Please certAlias configured for the Handler!"); if ((getOption("keyStoreType")) != null) keyStoreType = (String)getOption("keyStoreType"); if ((getOption("trustStoreType")) != null) trustStoreType = (String)getOption("trustStoreType"); } public void invoke(MessageContext messageContext) throws AxisFault { //do nothing now! } public void onFault(MessageContext msgContext) { System.out.println("处理错误,这里忽略!"); } }
2、写客户端的配置代码client-config.wsdd,如下:
<?xml version="1.0" encoding="UTF-8"?> <deployment name="defaultClientConfig" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="" target="_blank">http://xml.apache.org/axis/wsdd/providers/java"> <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/> <transport name="local" pivot="java:org.apache.axis.transport.local.LocalSender"/> <transport name="java" pivot="java:org.apache.axis.transport.java.JavaSender"/> <globalConfiguration> <requestFlow> <handler type="java:com.ronghao.WSAxis.WSClientRequestHandler"> <parameter name="keyStoreFile" value="D:\Tomcat5.5\webapps\axis\WEB-INF\client.keystore"/> <parameter name="keyEntryPassword" value="changeit"/> <parameter name="certAlias" value="serverkey"/> <parameter name="trustStorePassword" value="changeit"/> <parameter name="trustStoreFile" value="D:\Tomcat5.5\webapps\axis\WEB-INF\client.truststore"/> <parameter name="keyAlias" value="Client"/> <parameter name="keyStorePassword" value="changeit"/> </handler> </requestFlow> <responseFlow> <handler type="java:com.ronghao.WSAxis.WSClientResponseHandler"> <parameter name="keyStoreFile" value="D:\Tomcat5.5\webapps\axis\WEB-INF\client.keystore"/> <parameter name="keyEntryPassword" value="changeit"/> <parameter name="certAlias" value="serverkey"/> <parameter name="trustStorePassword" value="changeit"/> <parameter name="trustStoreFile" value="D:\Tomcat5.5\webapps\axis\WEB-INF\client.truststore"/> <parameter name="keyAlias" value="Client"/> <parameter name="keyStorePassword" value="changeit"/> </handler> </responseFlow> </globalConfiguration> </deployment>
同样不再解释,不明白可以参考我的上一篇文章
3、修改OrderClient.class
在OrderClient.class中加入了如下代码:
EngineConfiguration conf = new FileProvider("F:\\Tomcat\\webapps\\axis\\WEB-INF\\client-config.wsdd");//位置 Service service = new Service(conf); 当然记得导入 import org.apache.axis.EngineConfiguration; import org.apache.axis.configuration.FileProvider;
运行一下,返回"ronghao",靠,搞定!
注意:这次我把OrderClient.class的调用放到了一个JSP文件中而不是jbuilder中,因为有client-config.wsdd,所以你必须有完整的WEB程序发布到TOMCAT中,否则会报找不到
相应文件。
转载自:https://www.cnblogs.com/mingzi/archive/2009/03/22/1419120.html