最簡單的發佈AXIS的Web Service

1,簡單介紹

本文並不是想介紹Web服務的原理、系統架構等,我假設您已經瞭解了關於Web服務的一些基本的概念、原理等知識。本文主要是針對那些已經瞭解Web服務概念,但是還沒有親身體會Web服務所帶來令人歡欣鼓舞的特徵的開發人員。在此我認爲你已經具備了Java、XML等基礎知識。

2,WEB服務介紹

雖然我並不想詳細講述Web服務的體系結構,但是大概的介紹一下還是有必要的。Web服務是一種新型的Web應用程序。不同於其他Web應用程序,它是自適應、自我描述、模塊化的應用程序,並可以跨越Web進行發佈、定位以及調用。簡單的Web服務可以提供例如天氣預報或者航班信息的服務。一旦部署了Web服務,其他的應用程序就可以發現和調用所部署的服務。

3,AXIS項目介紹

Axis框架來自 Apache 開放源代碼組織,它是基於JAVA語言的最新的 SOAP 規範(SOAP 1.2)和 SOAP with Attachments 規範(來自 Apache Group )的開放源代碼實現。有很多流行的開發工具都使用AXIS作爲其實現支持Web服務的功能,例如JBuilder以及著名的Eclipse J2EE插件Lomboz。AXIS的最新版本是2.1,但是本文仍然採用成熟的版本1.4,可以從 http://ws.apache.org/axis/index.html下載。下圖是AXIS核心引擎的體系結構圖:

 

 

4,環境的搭建


由於AXIS本身是基於JAVA語言開發的項目,並且是以Web應用形式發佈的,因此它運行時需要一個應用服務器作爲支撐。爲了方便我這裏選用的是Tomcat。由於AXIS本身需要用到處理XML信息的包,所以我建議使用最新JDK1.6並安裝Tomcat 6.0。下面是環境搭建步驟,讀取根據自身情況進行安裝。

  1. 安裝JDK1.6,並配置好JDK的環境變量
  2. 安裝Tomcat 6.0,啓動Tomcat看是否能正常訪問。
  3. 下載AXIS項目打包文件axis-bin-1_4.zip解壓縮後將目錄中的webapps目錄下的axis子目錄拷貝到%TOMCAT_HOME%"webapps下。
  4. 配置的所有環境變量如下,用戶可以根據自己的電腦的具體情況配置好以下環境變量:
  5. JAVA_HOME    C:"Program Files"Java"jdk1.6.0_05
  6. Path C:"Program Files"Java"jdk1.6.0_05"bin;%SystemRoot%"system32;%SystemRoot%;%SystemRoot%"System32"Wbem;C:"Program Files"Microsoft SQL Server"80"Tools"BINN
  7. CATALINA_HOME   D:"Tomcat 6.0
  8. AXIS_HOME D:"software"Axis"axis-bin-1_4"axis-1_4
  9. AXIS_LIB %AXIS_HOME%"lib
  10. AXISCLASSPATH %AXIS_LIB%"axis.jar;%AXIS_LIB%"axis-ant.jar;%AXIS_LIB%"commons-discovery-0.2.jar;%AXIS_LIB%"commons-logging-1.0.4.jar;%AXIS_LIB%"jaxrpc.jar;%AXIS_LIB%"log4j-1.2.8.jar;%AXIS_LIB%"saaj.jar;%AXIS_LIB%"wsdl4j-1.5.1.jar;%AXIS_LIB%"activation.jar;%AXIS_LIB%"mail.jar;

驗證AXIS的安裝:重新啓動Tomcat服務器後打開瀏覽器輸入網址http://localhost:8080/axis 後應該出現如下圖所示頁面:

 

 

 

點擊鏈接"Validate"來驗證Axis所需的幾個JAVA包是否齊全。點擊超鏈接Validate後,AXIS會自動檢查所需的每一個JAVA組件,這協組件分爲:必需組件以及可選組件,必須保證所有必需組件都存在,如下圖所示即爲驗證成功。如下圖:

 

 

 

AXIS發佈方式介紹:

Axis支持三種web service的部署和開發,分別爲:

  1、Dynamic Invocation Interface (DII)

  2、Dynamic Proxy方式

  3、Stubs方式

這裏先介紹其中比較簡單的兩種,這兩種在實際的工程開發中應用並不多見,但容易學習,第三種是在工程中經常用到的,將在下一篇中做介紹:

1,Dynamic Invocation Interface動態調用接口

這個也稱之爲即時發佈,是Axis的特色之一,使用即時發佈使用戶只需有提供服務的Java類的源代碼,即可將其迅速發佈成Web服務。每當用戶調用這類服務的時候,Axis會自動進行編譯,即使服務器重啓了也不必對其做任何處理,使用非常簡單快捷。

  使用即時發佈首先需要一個實現服務功能的Java源文件,將其擴展名改爲.jwsJava Web Service的縮寫),然後將該文件放到“……"webapps"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 );
   }
}

注意項目中要導入其自帶的AXIS包(當然應該把其中JAR文件替換一下),可以看到程序返回了 "HELLO WORLD!"

可以看到在AXIS裏發佈服務其實是一件很容易的事,這是因爲這個服務很簡單的原因。

2,Dynamic Proxy動態代理方式

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="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,你就會看到你的服務已發佈

同樣用客戶端程序訪問一下:(注意和上邊的差別!!)

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 );
   }
}

對於有自定義的參數的客戶端調用方式如下:

String endpoint = "http://192.168.0.3/DataManager/services/User";
    Service service= new Service();
    Call call= (Call)service.createCall(); 
    call.setTargetEndpointAddress(new java.net.URL(endpoint));
    call.setOperationName("addUser");  

//前面都一樣,但是這裏加了一段註冊參數類型的說明,如果有多個自定義參數只需要要複製這段代碼,

//再修改參數就行了。
    QName qn = new QName("urn:beanservice", "User");
    call.registerTypeMapping(User.class, qn,    
      new BeanSerializerFactory(User.class, qn),    
      new BeanDeserializerFactory(User.class, qn));

         User user = new User();
         user.setClass_("U");
         user.setName_("annlee");
         user.setEmail_("[email protected]");
         user.setSeq_(new Integer(65546));
         user.setPassword_("password");
         user.setEnabled_("Y");
         user.setDisplayname_("李飛虎");
   
    String result=(String )call.invoke(new Object[]{user});

好了,相信你對AXIS已有了大致的瞭解。接下來將會涉及到Stub方式調用,及AXIS的安全問題,AOP編程方面的知識。

 

 
AXIS第二課:工程應用中的AXIS的發佈方法Stub

工程應用當中的web service的參數和通回值通常都是一個數據Bean類,因此前面介紹的兩種發佈AXIS的web service方法在工程應用當中並不多見,下面介紹Stub發佈方法,開發步驟如下:

1,編寫服務端程序UserEndpoint.java

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.remoting.jaxrpc.ServletEndpointSupport;

import com.miracle.dm.framework.organization.dao.hibernate.UserHibernateDAO;
import com.miracle.dm.framework.organization.model.User;

public class UserEndpoint extends ServletEndpointSupport{
protected Logger logger = LogManager.getLogger(UserEndpoint.class);
private UserHibernateDAO userHibernateDAO; 

protected void onInit(){
   userHibernateDAO = (UserHibernateDAO)getWebApplicationContext()
                             .getBean("userHibernateDAO");

}

/**
* 增加User到數據庫

* @return 返回新增加的數據的ID,失敗返回字符串“FAILED”
*/
public String addUser(User user){
   try{
    String rev = userHibernateDAO.addUser(user);
    return rev;
   }catch(Exception e){
    logger.error("use web service to add user failed , user Name is : " + user.getName_());
    return "FAILED";
   }
}

/**
* 從數據庫刪除User

* @return 刪除成功返回字符串“OK” ,失敗返回字符串“FAILED”
*/
public String delUser(String userId){
   try{
    userHibernateDAO.delUser(userId);
    return "OK";
   }catch(Exception e){
    logger.error("use web service to delete user failed , user Id is : " + userId);
    return "FAILED";
   }
  
}

/**
* 將參數中的數據更新到數據庫

* @return 成功返回字符串“OK”,失敗返回字符串“FAILED”
*/
public String updateUser(User user){
   try{
    userHibernateDAO.updateUser(user);
    return "OK";
   }catch(Exception e){
    logger.error("use web service to update user failed , user Id is : " + user.getID_());
    return "FAILED";
   }
}
}

2,將Axis集成到工程當中

在工程的web.xml配置文件中加入以下AXIS配置:此配置加在<web-app>節點下。

<!-- AXIS配置 -->
    <listener>
        <listener-class>org.apache.axis.transport.http.AxisHTTPSessionListener</listener-class>
    </listener>
    
<servlet>
    <servlet-name>AxisServlet</servlet-name>
    <display-name>Apache-Axis Servlet</display-name>
    <servlet-class>
        org.apache.axis.transport.http.AxisServlet
    </servlet-class>
</servlet>

<servlet>
    <servlet-name>AdminServlet</servlet-name>
    <display-name>Axis Admin Servlet</display-name>
    <servlet-class>
        org.apache.axis.transport.http.AdminServlet
    </servlet-class>
    <load-on-startup>100</load-on-startup>
</servlet>

<servlet>
    <servlet-name>SOAPMonitorService</servlet-name>
    <display-name>SOAPMonitorService</display-name>
    <servlet-class>
        org.apache.axis.monitor.SOAPMonitorService
    </servlet-class>
    <init-param>
      <param-name>SOAPMonitorPort</param-name>
      <param-value>5001</param-value>
    </init-param>
    <load-on-startup>100</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>AxisServlet</servlet-name>
    <url-pattern>/servlet/AxisServlet</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>AxisServlet</servlet-name>
    <url-pattern>*.jws</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>AxisServlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>SOAPMonitorService</servlet-name>
    <url-pattern>/SOAPMonitor</url-pattern>
</servlet-mapping>

<!-- uncomment this if you want the admin servlet -->
<!--
<servlet-mapping>
    <servlet-name>AdminServlet</servlet-name>
    <url-pattern>/servlet/AdminServlet</url-pattern>
</servlet-mapping>
-->

    <session-config>
        <!-- Default to 5 minute session timeouts -->
        <session-timeout>5</session-timeout>
    </session-config>

    <!-- currently the W3C havent settled on a media type for WSDL;
    http://www.w3.org/TR/2003/WD-wsdl12-20030303/#ietf-draft
    for now we go with the basic 'it's XML' response -->
<mime-mapping>
    <extension>wsdl</extension>
     <mime-type>text/xml</mime-type>
</mime-mapping>

<mime-mapping>
    <extension>xsd</extension>
    <mime-type>text/xml</mime-type>
</mime-mapping>

3,編寫wsdd文件

  deploy.wsdd文件,此文件跟上面的類文件放在同一個包下,內容如下:

<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="UserEndpoint" provider="java:RPC">
<parameter name="className" value="UserEndpoint"/>
<parameter name="allowedMethods" value="*"/>
</service>
</deployment>

4、發佈服務:

 在DOS下轉換目錄到上面的包目錄,執行以下命令:

java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient deploy.wsdd

你會發現目錄下多了一個server-config.wsdd文件,這就是AXIS的配置文件,以後所有的服務發佈描述都會在裏面找到。(當然你可以不用寫deploy.wsdd,而是直接創建並修改它,以後新加入的web service也可以在裏面添加而不用執行以上命令)。

server-config.wsdd文件的標準內容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> 
<handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/> 
<service name="Document" provider="java:RPC"> 
<parameter name="className" value="com.miracle.dm.service.DocumentEndpoint"/> 
<parameter name="allowedMethods" value="*"/>   
        
<beanMapping qname="ns:DocumentInfo" xmlns:ns="urn:beanservice"
             languageSpecificType="java:com.miracle.dm.doc.document.model.DocumentInfo"/> 
    <beanMapping qname="ns:DocExtpropInfo" xmlns:ns="urn:beanservice"
             languageSpecificType="java:com.miracle.dm.doc.document.model.DocExtpropInfo"/>
             
    <beanMapping qname="ns:DocAttachmentInfo" xmlns:ns="urn:beanservice"
             languageSpecificType="java:com.miracle.dm.doc.document.model.DocAttachmentInfo"/>
             
    <beanMapping qname="ns:DoccatRelatInfo" xmlns:ns="urn:beanservice"
             languageSpecificType="java:com.miracle.dm.doc.document.model.DoccatRelatInfo"/> 
             
    <typeMapping qname="ns:DataHandler" xmlns:ns="urn:beanservice" 
          languageSpecificType="java:javax.activation.DataHandler" 
          serializer="org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory" 
          deserializer="org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory" 
          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/%22/>
                   
</service>

<transport name="http"> 
<requestFlow> 
<handler type="URLMapper"/> 
</requestFlow> 
</transport>

</deployment>

其中的beanmapping節點都是在此服務中應用到的Bean的配置。

此時你訪問http://localhost:8080/工程名/services就可以看到此工程發佈的所有的web service

5,生成客戶端client stub文件

  在瀏覽器上訪問服務器端的服務,可以下載到User.wsdl文件,保存到E盤,通過Axis的相關工具,可以自動從WSDL文件中生成Web Service的客戶端代碼。

  編寫一個WSDL2Java.bat文件,其內容如下:

set Axis_Lib=D:\software\Axis\axis-bin-1_4\axis-1_4\lib
set Java_Cmd=java -Djava.ext.dirs=%Axis_Lib%
set Output_Path=D:\project\MiracleDataManager\src
set Package=com.miracle.dm.service.client
%Java_Cmd% org.apache.axis.wsdl.WSDL2Java -o%Output_Path% -p%Package% User.wsdl
cmd

  注意,.bat文件中的路徑不能有空格和中文字符,執行這個批處理文件就可以生成client stub.

  生成的stub client文件列表爲:GenericValue.java,User.java,UserEndpoint.java,UserEndpointService.java,UserEndpointServiceLocator.java,UserSoapBindingStub.java

5,客戶端調用例子

這裏我使用Junit來測試:

package com.miracle.dm.service.test;

import com.miracle.dm.service.client.User;
import com.miracle.dm.service.client.UserEndpoint;
import com.miracle.dm.service.client.UserEndpointService;
import com.miracle.dm.service.client.UserEndpointServiceLocator;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class TestUserClient extends TestCase {

    public TestUserClient(String string) {
        super(string);
    }

    public void addUser() throws Exception {
    
        UserEndpointService service = new UserEndpointServiceLocator();
        UserEndpoint client = service.getUser();
        User user = new User();
        user.setClass_("U");
        user.setName_("annlee");
        user.setEmail_("[email protected]");
        user.setSeq_(65546);
        user.setPassword_("password");
        user.setEnabled_("Y");
        user.setDisplayname_("李飛虎");
        user.setDomain("100");
        user.setMobile("12345");
        String retValue = client.addUser(user);
        System.out.println(retValue);

    }
    
    public void updateUser() throws Exception{
    UserEndpointService service = new UserEndpointServiceLocator();
        UserEndpoint client = service.getUser();
        User user = new User();
        user.setID_("402880ea1b447d08011b447e3e880002");
        user.setClass_("U");
        user.setName_("annlee");
        user.setEmail_("[email protected]");
        user.setSeq_(65546);
        user.setPassword_("password");
        user.setEnabled_("Y");
        user.setDisplayname_("李飛虎");
        user.setDomain("100");
        user.setMobile("12345");
        String retValue = client.updateUser(user);
        System.out.println(retValue);
    }
    
    public void delUser() throws Exception{
    UserEndpointService service = new UserEndpointServiceLocator();
        UserEndpoint client = service.getUser();
        String retValue = client.delUser("402880ea1b447d08011b447e3e880002");
        System.out.println(retValue);
    }

    public static Test suite() {
        TestSuite suite = new TestSuite();
//      suite.addTest(new TestUserClient("addUser"));
   //     suite.addTest(new TestUserClient("updateUser"));
        suite.addTest(new TestUserClient("delUser"));
        return suite;
    }
}

至此,整個服務器端和客戶端的Web Service框架代碼就完成了,剩下的就是在UserEndpoint.java文件裏面加入你的業務代碼了。

AXIS第三課:AXIS高級應用,使用Handler來增強Web服務的功能

 

1,AXIS提供的工具

Apache Axis提供了WSDL2Java和Java2WSDL兩個開發工具。

WSDL2Java利用已知的WSDL文件生成服務端和客戶端代碼。該WSDL文件可以是由合作伙伴提供的,也可以是利用Java2WSDL生成的。Java2WSDL根據已有的Java類文件生成WSDL文件,Java類文件可以是接口類文件,並不需要實現細節。

此外Axis還提供了SoapMonitorApplet和TCPMon工具,可用於監測Web服務。

使用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消息時,Handler把請求消息中的某些敏感的信息(如信用卡密碼)進行加密,然後把加密後的SOAP消息傳輸到服務端;服務端的SOAP消息Handler截取客戶端的請求,把請求的SOAP 消息進行解密,然後把解密後的SOAP消息派發到目標的Web服務端點。

Apache axis是我們當前開發Web服務的較好的選擇,使用axisWeb服務開發工具,可以使用Handler來對服務端的請求和響應進行處理。典型的情況下,軸心點(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中的public abstract void invoke(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

將會提示輸入用戶名和密碼。

訪問web服務時的驗證,如果客戶端是應用程序,那麼可以這樣在客戶端設置用戶名和密碼:

例程6 在客戶端設置用戶名和密碼


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%22/>
<ns3:DigestValue>rO0ABXQAkyA8Y2FyZ…….
</ns3:DigestValue>
</ns3:EncryptedData>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>


使用Handler對SOAP消息進行加密、解密後,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代碼如下:


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高級應用,建立安全的AXIS服務

 

在前面的文章中,我們實現了最簡單的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界面,在
eclipse中運行我們上次寫的客戶端程序 TestClient.java。OK!你會在Applet界面看
見客戶端與服務器端互發的XML內容,注意這裏是明文!

二、使用axis的Handler進行訪問控制(對安全要求不高時推薦)
axis爲Web服務的訪問控制提供了相關的配置描述符,並且提供了一個訪問控制的簡單 Handler。默認情況下,你只要在配置描述符中添加用戶,然後在Web服務器的部署描述符中自動允許的角色即可。

1、在axis的配置文件users.lst(位於WEB-INF目錄下)中添加一個用戶,如"annlee 1111",表示
用戶名爲annlee,密碼爲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="annlee"/>   //注意,這裏是新加的部分!
<parameter name="className" value="HelloWorld"/>
</service>
在這個部署描述符中,指定HelloWorld服務只能被annlee訪問

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("annlee");// 用戶名。
    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

 

四、使用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.annlee.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.annlee.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.annlee.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/。不過這個東西也應該夠用了暫時。

AXIS第五課:AXIS高級應用,在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("annlee");
    else newOrder.setName("leeann");
    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.annlee.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!運行一下,就可以看到返回了"annlee"。

和上一篇文章一樣,我們不容許在網絡中傳遞XML是明文,於是需要加密和驗證。這裏我們繼續採用上次所講的框架。(已打包成ws-axis.jar)

一、修改服務器端配置文件server-config.wsdd(和上一文章一模一樣!不再羅嗦)
在server-config.wsdd中相應位置添加以下代碼
      <requestFlow>
      <handler type="soapmonitor"/>
      <handler type="java:com.annlee.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.annlee.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
&apos;newProfileReturn&apos;: 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.annlee.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.annlee.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;

運行一下,返回"annlee",靠,搞定!
注意:這次我把OrderClient.class的調用放到了一個JSP文件中而不是jbuilder中,因爲有client-config.wsdd,所以你必須有完整的WEB程序發佈到TOMCAT中,否則會報找不到相應文件。

The project was not built since its build path is incomplete. Cannot find the class file

遇到The type XXX cannot be resolved. It is indirectly referenced from required .class files錯誤.....,查找的解決辦法如下:

錯誤提示:The project was not built since its build path is incomplete. Cannot find the class file for java.lang.Object. Fix the build path then try building this project 
The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files 

今天在eclipse3.2+myeclipse5.1+tomcat5.5重新部署時出了這問題.搞了很久才找到原因.解決辦法寫出來分享:

出現以上錯誤的原因是居然是裝jdk5時了多裝了個jre。本來Eclipse在建立項目時,會自動參照你的jre路徑,但多個版本就沒辦法加載了。 
解決辦法:
1. 
進入window \ preferences \ java \ Installed JREs 
1)
Add 
2)
輸入JRE Name, JDK1.5.0.09
3)JRE home directory, 
選擇安裝的路徑 
4)
OK 
2. 
進入Project \ properties \ Java Bulid Path 
1)Add library 
2)
JRE System Library後按Next 
3)
workplace default JRE後按 finish... 

這樣就行了。

 

一: 錯誤提示: It is indirectly referenced from required .class file 錯誤的解決

原因:你正要使用的類調用了另一個類,而這個類又調用了其他類,這種關係可能會有好多層。而在這個調用的過程中,某個類所在的包的缺失就會造成以上那個錯誤。

解決方法:導入缺失的包

發佈了1 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章