Handler的基本概念
J2EE Web 服務(wù)中的Handler技術(shù)特點非常像Servlet技術(shù)中的Filter。我們知道,在Servlet中,當一個HTTP到達服務(wù)端時,往往要經(jīng)過多個Filter對請求進行過濾,然后才到達提供服務(wù)的Servlet,這些Filter的功能往往是對請求進行統(tǒng)一編碼,對用戶進行認證,把用戶的訪問寫入系統(tǒng)日志等。相應(yīng)的,Web服務(wù)中的Handler通常也提供一下的功能:
- 對客戶端進行認證、授權(quán);
- 把用戶的訪問寫入系統(tǒng)日志;
- 對請求的SOAP消息進行加密,解密;
- 為Web Services對象做緩存。
SOAP消息Handler能夠訪問代表RPC請求或者響應(yīng)的SOAP消息。在JAX-RPC技術(shù)中,SOAP消息Handler可以部署在服務(wù)端,也可以在客戶端使用。
下面我們來看一個典型的SOAP消息Handler處理順序:
某個在線支付服務(wù)需要防止非授權(quán)的用戶訪問或者撰改服務(wù)端和客戶端傳輸?shù)男畔?,從而使用消息摘要(Message Digest)的方法對請求和響應(yīng)的SOAP消息進行加密。當客戶端發(fā)送SOAP消⑹?,客户墩f腍andler把請求消息中的某些敏感的信息(如信用卡密碼)進行加密,然后把加密后的SOAP消息傳輸?shù)椒?wù)端;服務(wù)端的SOAP消息Handler截取客戶端的請求,把請求的SOAP 消息進行解密,然后把解密后的SOAP消息派發(fā)到目標的Web服務(wù)端點。
Apache axis是我們當前開發(fā)Web服務(wù)的較好的選擇,使用axisWeb服務(wù)開發(fā)工具,可以使用Handler來對服務(wù)端的請求和響應(yīng)進行處理。典型的情況下,請求傳遞如圖1所示。
圖1 SOAP消息的傳遞順序
在圖中,軸心點(pivot point)是Apache與提供程序功能相當?shù)牟糠?,通過它來和目標的Web服務(wù)進行交互,它通常稱為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)境下運行。
在axis環(huán)境下,SOAP消息Handler必須實現(xiàn)org.apache.axis.Handler接口(在JAX-RPC 1.0規(guī)范中,SOAP消息Handler必須實現(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時,只要繼承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中實現(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實現(xiàn)類必須實現(xiàn)的方法,它通過MessageContext來獲得請求或者響應(yīng)的SOAPMessage對象,然后對SOAPMessage進行處理。
在介紹Handler的開發(fā)之前,我們先來看一下目標Web服務(wù)的端點實現(xiàn)類的代碼,如例程3所示。
例程3 目標Web服務(wù)的端點實現(xiàn)類package com.hellking.webservice;public class HandleredService { //一個簡單的Web服務(wù) public String publicMethod(String name) { return "Hello!"+name; }}//另一個Web服務(wù)端點:package com.hellking.webservice;public class OrderService { //web服務(wù)方法:獲得客戶端的訂單信息,并且對訂單信息進行對應(yīng)的處理,通常情況是把訂單的信息寫入數(shù)據(jù)庫,然后可客戶端返回確認信息。 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的使用實例。
使用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都必須實現(xiàn)的方法。 */ public void invoke(MessageContext msgContext) throws AxisFault { //每當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實現(xiàn)類必須實現(xiàn)invoke方法,invoke方法是Handler處理其業(yè)務(wù)的入口點。LogHandler的主要功能是把客戶端訪問的Web服務(wù)的名稱和訪問時間、訪問的次數(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對用戶的訪問認證
使用Handler為用戶訪問認證也是它的典型使用,通過它,可以減少在Web服務(wù)端代碼中認證的麻煩,同時可以在部署描述符中靈活改變用戶的訪問權(quán)限。
對用戶認證的Handler代碼如下:
例程5 認證的Handlerpackage com.hellking.webservice;import….//此handler的目的是對用戶認證,只有認證的用戶才能訪問目標服務(wù)。public class AuthenticationHandler extends BasicHandler{ /**invoke,每一個handler都必須實現(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(); //對用戶進行認證,如果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); //用戶通過認證,把用戶的設(shè)置成認證了的用戶。 msgContext.setProperty("authenticatedUser", authUser); } }}
|
在AuthenticationHandler代碼里,它從MessageContext中獲得用戶信息,然后進行認證,如果認證成功,那么就使用msgContext.setProperty("authenticatedUser", authUser)方法把用戶設(shè)置成認證了的用戶,如果認證不成功,那么就拋出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 hellkingchen chen
|
http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen
|
將會提示輸入用戶名和密碼,如圖2所示。
圖2 訪問web服務(wù)時的驗證
如果客戶端是應(yīng)用程序,那么可以這樣在客戶端設(shè)置用戶名和密碼:
例程6 在客戶端設(shè)置用戶名和密碼http://127.0.0.1:808String 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è)置操作的名稱。 //由于需要認證,故需要設(shè)置調(diào)用的用戶名和密碼。 call.getMessageContext().setUsername("chen"); call.getMessageContext().setPassword("chen");
|
使用Handler對用戶的訪問授權(quán)
對于已經(jīng)認證了的用戶,有時在他們操作某個特定的服務(wù)時,還需要進行授權(quán),只有授權(quán)的用戶才能繼續(xù)進行操作。我們看對用戶進行授權(quán)的Handler的代碼。
例程7 對用戶進行授權(quán)的代碼package com.hellking.webservice;import…//此handler的目的是對認證的用戶授權(quán),只有授權(quán)的用戶才能訪問目標服務(wù)。public class AuthorizationHandler extends BasicHandler{ /**invoke,每一個handler都必須實現(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),不能訪問目標服務(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消息進行加密、解密
由于SOAP消息在HTTP協(xié)議中傳輸,而HTTP協(xié)議的安全度是比較低的,怎么保證信息安全到達對方而不泄漏或中途被撰改,將是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ù),要使信息安全到達對方,必須把它進行加密,然后在對方收到信息后解密。為了提供開發(fā)的方便,可以使用Handler技術(shù),在客戶端發(fā)送信息前,使用客戶端的Handler對SOAP消息中的關(guān)鍵信息進行加密;在服務(wù)端接收到消息后,有相應(yīng)的Handler把消息進行解密,然后才把SOAP消息派發(fā)到目標服務(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ù)拿舾行畔?,故需要加密。我們可以使用Message Digest之類的方法進行加密。加密之后的信息結(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消息進行加密、解密后,SOAP消息在傳遞過程中結(jié)構(gòu)的改變。
圖3 SOAP消息的加密和解密
從上圖可以看出,通過使用加密、解密的Handler,可以確保消息的安全傳遞。進一步說,如果把這種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消息時,客戶端的Handler攔截發(fā)送的SOAP消息,然后對它們進行加密,最后把加密的信息傳送到服務(wù)端。
服務(wù)端接收到加密的信息后,解密的Handler會把對應(yīng)的加密信息解密。服務(wù)端Handler代碼如例程11所示。
例程11 服務(wù)端解密Handlerpackage com.hellking.webservice;import…//此handler的目的是把加密的SOAP消息解密成目標服務(wù)可以使用的SOAP消息。public class MessageDigestHandler extends BasicHandler{ /**invoke,每一個handler都必須實現(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解密成目標服務(wù)可以調(diào)用的SOAP消息。 SOAPMessage msg2=convertMessage(msg,this.decrypte(value)); msgContext.setMessage(msg2); } catch(Exception e) { e.printStackTrace(); } } //這個方法是把加密的數(shù)據(jù)進行解密,返回明文。 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ù)提供一些額外的功能。在實際的開發(fā)中,我們可以開發(fā)出一些通用的Handler,然后通過不同的搭配方式把它們部署到不同的Web服務(wù)中。
另外,在XML & Web services專區(qū)還有一篇關(guān)于使用Handler來為Web服務(wù)提供緩存功能的文章,您可以參考它, 《用高速緩存加速您的 Web 服務(wù)》