AXIS完全總結

AXIS完全總結

關鍵詞AXIS        

目錄

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程序員,聯繫:)[email protected] 

第三種方式:

 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
  &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.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中,否則會報找不到
相應文件。 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章