本文是J2EE Web服務(wù)開發(fā)系列文章的第六篇,本文從SOAP消息中Handler的基本概念入手,逐步深入討論Handler的各種典型使用(生成日志、用戶認(rèn)證、用戶授權(quán)、信息加密/解密)以及實(shí)現(xiàn)方法。
閱讀本文前您需要以下的知識和工具:
- Apache axis1.1,并且會初步使用;
- Tomcat 4.0以上, 并且會初步使用;
- SOAP消息(SOAP Message)編程知識;
- JAX-RPC編程基礎(chǔ)知識;
- Java安全編程基礎(chǔ)知識。
本文的參考資料見
參考資料。
本文的全部代碼在這里
下載。
Handler的基本概念
J2EE Web 服務(wù)中的Handler技術(shù)特點(diǎn)非常像Servlet技術(shù)中的Filter。我們知道,在Servlet中,當(dāng)一個HTTP到達(dá)服務(wù)端時(shí),往往要經(jīng)過多個Filter對請求進(jìn)行過濾,然后才到達(dá)提供服務(wù)的Servlet,這些Filter的功能往往是對請求進(jìn)行統(tǒng)一編碼,對用戶進(jìn)行認(rèn)證,把用戶的訪問寫入系統(tǒng)日志等。相應(yīng)的,Web服務(wù)中的Handler通常也提供一下的功能:
- 對客戶端進(jìn)行認(rèn)證、授權(quán);
- 把用戶的訪問寫入系統(tǒng)日志;
- 對請求的SOAP消息進(jìn)行加密,解密;
- 為Web Services對象做緩存。
SOAP消息Handler能夠訪問代表RPC請求或者響應(yīng)的SOAP消息。在JAX-RPC技術(shù)中,SOAP消息Handler可以部署在服務(wù)端,也可以在客戶端使用。
下面我們來看一個典型的SOAP消息Handler處理順序:
某個在線支付服務(wù)需要防止非授權(quán)的用戶訪問或者撰改服務(wù)端和客戶端傳輸?shù)男畔ⅲ瑥亩褂孟⒄∕essage Digest)的方法對請求和響應(yīng)的SOAP消息進(jìn)行加密。當(dāng)客戶端發(fā)送SOAP消⑹保突Ф說腍andler把請求消息中的某些敏感的信息(如信用卡密碼)進(jìn)行加密,然后把加密后的SOAP消息傳輸?shù)椒?wù)端;服務(wù)端的SOAP消息Handler截取客戶端的請求,把請求的SOAP 消息進(jìn)行解密,然后把解密后的SOAP消息派發(fā)到目標(biāo)的Web服務(wù)端點(diǎn)。
Apache axis是我們當(dāng)前開發(fā)Web服務(wù)的較好的選擇,使用axisWeb服務(wù)開發(fā)工具,可以使用Handler來對服務(wù)端的請求和響應(yīng)進(jìn)行處理。典型的情況下,請求傳遞如圖1所示。

圖1 SOAP消息的傳遞順序
在圖中,軸心點(diǎn)(pivot point)是Apache與提供程序功能相當(dāng)?shù)牟糠郑ㄟ^它來和目標(biāo)的Web服務(wù)進(jìn)行交互,它通常稱為Provider。axis中常用的Provider有Java:RPC,java:MSG,java:EJB。一個Web服務(wù)可以部署一個或者多個Handler。
Apache axis中的Handler體系結(jié)構(gòu)和JAX-RPC 1.0(JSR101)中的體系結(jié)構(gòu)稍有不同,需要聲明的是,本文的代碼在axis中開發(fā),故需要在axis環(huán)境下運(yùn)行。
在axis環(huán)境下,SOAP消息Handler必須實(shí)現(xiàn)org.apache.axis.Handler接口(在JAX-RPC 1.0規(guī)范中,SOAP消息Handler必須實(shí)現(xiàn)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;
…
}
|
為了提供開發(fā)的方便,在編寫Handler時(shí),只要繼承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中實(shí)現(xiàn)。
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實(shí)現(xiàn)類必須實(shí)現(xiàn)的方法,它通過MessageContext來獲得請求或者響應(yīng)的SOAPMessage對象,然后對SOAPMessage進(jìn)行處理。
在介紹Handler的開發(fā)之前,我們先來看一下目標(biāo)Web服務(wù)的端點(diǎn)實(shí)現(xiàn)類的代碼,如例程3所示。
例程3 目標(biāo)Web服務(wù)的端點(diǎn)實(shí)現(xiàn)類package com.hellking.webservice;
public class HandleredService
{
//一個簡單的Web服務(wù)
public String publicMethod(String name)
{
return "Hello!"+name;
}
}
//另一個Web服務(wù)端點(diǎn):
package com.hellking.webservice;
public class OrderService
{
//web服務(wù)方法:獲得客戶端的訂單信息,并且對訂單信息進(jìn)行對應(yīng)的處理,
通常情況是把訂單的信息寫入數(shù)據(jù)庫,然后可客戶端返回確認(rèn)信息。
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("這里是客戶端發(fā)送來的信息:");
System.out.println(orderInfo);
return orderInfo;
}
}
|
下面我們分不同情況討論Handler的使用實(shí)例。
使用Handler為系統(tǒng)做日志
Handler為系統(tǒng)做日志是一種比較常見而且簡單的使用方式。和Servlet中的Filter一樣,我們可以使用Handler來把用戶的訪問寫入系統(tǒng)日志。下面我們來看日志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都必須實(shí)現(xiàn)的方法。
*/
public void invoke(MessageContext msgContext) throws AxisFault
{
//每當(dāng)web服務(wù)被調(diào)用,都記錄到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 服務(wù) " +
msgContext.getTargetService() +
" 被調(diào)用,現(xiàn)在已經(jīng)共調(diào)用了 " + counter + " 次.";
handler.setOption("accesses", counter);
writer.println(result);
writer.close();
} catch (Exception e) {
throw AxisFault.makeFault(e);
}
}
}
|
前面我們說過,Handler實(shí)現(xiàn)類必須實(shí)現(xiàn)invoke方法,invoke方法是Handler處理其業(yè)務(wù)的入口點(diǎn)。LogHandler的主要功能是把客戶端訪問的Web服務(wù)的名稱和訪問時(shí)間、訪問的次數(shù)記錄到一個日志文件中。
下面部署這個前面開發(fā)的Web服務(wù)對像,然后為Web服務(wù)指定Handler。編輯Axis_Home/WEB-INF/ server-config.wsdd文件,在其中加入以下的內(nèi)容:
<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需要根據(jù)具體情況改變。
在Sun Jul 06 22:42:03 CST 2003: Web 服務(wù) HandleredService 被調(diào)用,現(xiàn)在已經(jīng)共調(diào)用了 1 次.
在Sun Jul 06 22:42:06 CST 2003: Web 服務(wù) HandleredService 被調(diào)用,現(xiàn)在已經(jīng)共調(diào)用了 2 次.
在Sun Jul 06 22:42:13 CST 2003: Web 服務(wù) HandleredService 被調(diào)用,現(xiàn)在已經(jīng)共調(diào)用了 3 次.
|
使用Handler對用戶的訪問認(rèn)證
使用Handler為用戶訪問認(rèn)證也是它的典型使用,通過它,可以減少在Web服務(wù)端代碼中認(rèn)證的麻煩,同時(shí)可以在部署描述符中靈活改變用戶的訪問權(quán)限。
對用戶認(rèn)證的Handler代碼如下:
例程5 認(rèn)證的Handlerpackage com.hellking.webservice;
import….
//此handler的目的是對用戶認(rèn)證,只有認(rèn)證的用戶才能訪問目標(biāo)服務(wù)。
public class AuthenticationHandler extends BasicHandler
{
/**invoke,每一個handler都必須實(shí)現(xiàn)的方法。
*/
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();
//對用戶進(jìn)行認(rèn)證,如果authUser==null,表示沒有通過認(rèn)證,
拋出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);
//用戶通過認(rèn)證,把用戶的設(shè)置成認(rèn)證了的用戶。
msgContext.setProperty("authenticatedUser", authUser);
}
}
}
|
在AuthenticationHandler代碼里,它從MessageContext中獲得用戶信息,然后進(jìn)行認(rèn)證,如果認(rèn)證成功,那么就使用msgContext.setProperty("authenticatedUser", authUser)方法把用戶設(shè)置成認(rèn)證了的用戶,如果認(rèn)證不成功,那么就拋出Server.Unauthenticated異常。
部署這個Handler,同樣,在server-config里加入以下的內(nèi)容:
<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服務(wù)時(shí)的驗(yàn)證
如果客戶端是應(yīng)用程序,那么可以這樣在客戶端設(shè)置用戶名和密碼:
例程6 在客戶端設(shè)置用戶名和密碼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") );//設(shè)置操作的名稱。
//由于需要認(rèn)證,故需要設(shè)置調(diào)用的用戶名和密碼。
call.getMessageContext().setUsername("chen");
call.getMessageContext().setPassword("chen");
|
使用Handler對用戶的訪問授權(quán)
對于已經(jīng)認(rèn)證了的用戶,有時(shí)在他們操作某個特定的服務(wù)時(shí),還需要進(jìn)行授權(quán),只有授權(quán)的用戶才能繼續(xù)進(jìn)行操作。我們看對用戶進(jìn)行授權(quán)的Handler的代碼。
例程7 對用戶進(jìn)行授權(quán)的代碼package com.hellking.webservice;
import…
//此handler的目的是對認(rèn)證的用戶授權(quán),只有授權(quán)的用戶才能訪問目標(biāo)服務(wù)。
public class AuthorizationHandler extends BasicHandler
{
/**invoke,每一個handler都必須實(shí)現(xiàn)的方法。
*/
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;//訪問授權(quán)通過。
}
}
//沒有通過授權(quán),不能訪問目標(biāo)服務(wù),拋出Server.Unauthorized異常。
throw new AxisFault("Server.Unauthorized",
Messages.getMessage("cantAuth02", userId, serviceName), null, null);
}
}
|
在service-config.wsdd文件中,我們?yōu)閃eb服務(wù)指定了以下的用戶:
<parameter name="allowedRoles" value="chen,hellking"/>
|
provider.userMatches(user, thisRole)將匹配允許訪問Web服務(wù)的用戶,如果匹配成功,那么授權(quán)通過,如果沒有授權(quán)成功,那么拋出Server.Unauthorized異常。
使用Handler對SOAP消息進(jìn)行加密、解密
由于SOAP消息在HTTP協(xié)議中傳輸,而HTTP協(xié)議的安全度是比較低的,怎么保證信息安全到達(dá)對方而不泄漏或中途被撰改,將是Web服務(wù)必須解決的問題。圍繞Web服務(wù)的安全,有很多相關(guān)的技術(shù),比如WS-Security,WS-Trace等,另外,還有以下相關(guān)技術(shù):
- XML Digital Signature(XML數(shù)字簽名)
- 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
不管使用什么技術(shù),要使信息安全到達(dá)對方,必須把它進(jìn)行加密,然后在對方收到信息后解密。為了提供開發(fā)的方便,可以使用Handler技術(shù),在客戶端發(fā)送信息前,使用客戶端的Handler對SOAP消息中的關(guān)鍵信息進(jìn)行加密;在服務(wù)端接收到消息后,有相應(yīng)的Handler把消息進(jìn)行解密,然后才把SOAP消息派發(fā)到目標(biāo)服務(wù)。
下面我們來看一個具體的例子。加入使用SOAP消息發(fā)送訂單的信息,訂單的信息如下:
例程8 要發(fā)送的訂單SOAP消息<soap-env:Envelope xmlns:soap-env="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>
|
上面的黑體字是傳輸?shù)拿舾行畔ⅲ市枰用堋N覀兛梢允褂肕essage Digest之類的方法進(jìn)行加密。加密之后的信息結(jié)構(gòu)如下:
例程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="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消息進(jìn)行加密、解密后,SOAP消息在傳遞過程中結(jié)構(gòu)的改變。

圖3 SOAP消息的加密和解密
從上圖可以看出,通過使用加密、解密的Handler,可以確保消息的安全傳遞。進(jìn)一步說,如果把這種Handler做成通用的組件,那么就可以靈活地部署到不同的服務(wù)端和客戶端。
客戶端的Handler的功能是把SOAP消息使用一定的規(guī)則加密,假如使用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));//對加密的信息編碼
|
在客戶端發(fā)送出SOAP消息時(shí),客戶端的Handler攔截發(fā)送的SOAP消息,然后對它們進(jìn)行加密,最后把加密的信息傳送到服務(wù)端。
服務(wù)端接收到加密的信息后,解密的Handler會把對應(yīng)的加密信息解密。服務(wù)端Handler代碼如例程11所示。
例程11 服務(wù)端解密Handlerpackage com.hellking.webservice;
import…
//此handler的目的是把加密的SOAP消息解密成目標(biāo)服務(wù)可以使用的SOAP消息。
public class MessageDigestHandler extends BasicHandler
{
/**invoke,每一個handler都必須實(shí)現(xiàn)的方法。
*/
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解密成目標(biāo)服務(wù)可以調(diào)用的SOAP消息。
SOAPMessage msg2=convertMessage(msg,this.decrypte(value));
msgContext.setMessage(msg2);
}
catch(Exception e)
{
e.printStackTrace();
}
}
//這個方法是把加密的數(shù)據(jù)進(jìn)行解密,返回明文。
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;
}
//把解密后的信息重新組裝成服務(wù)端能夠使用的SOAP消息。
public SOAPMessage convertMessage(SOAPMessage msg,String data)
{
….
}
}
|
可以看出,服務(wù)端解密的Handler和客戶端加密的Handler的操作是相反的過程。
總結(jié)
通過以上的討論,相信大家已經(jīng)掌握了Handler的基本使用技巧。可以看出,通過使用Handler,可以給Web服務(wù)提供一些額外的功能。在實(shí)際的開發(fā)中,我們可以開發(fā)出一些通用的Handler,然后通過不同的搭配方式把它們部署到不同的Web服務(wù)中。
另外,在XML & Web services專區(qū)還有一篇關(guān)于使用Handler來為Web服務(wù)提供緩存功能的文章,您可以參考它, 《用高速緩存加速您的 Web 服務(wù)》
posted on 2006-03-27 14:43
有貓相伴的日子 閱讀(301)
評論(0) 編輯 收藏