使用Handler来增强Web服务的功能

Handler的基本概念

J2EEWeb服务中的Handler技术特点非常像Servlet技术中的Filter。我们知道,在Servlet中,当一个HTTP到达服务端时,往往要经过多个Filter对请求进行过滤,然后才到达提供服务的Servlet,这些Filter的功能往往是对请求进行统一编码,对用户进行认证,把用户的访问写入系统日志等。相应的,Web服务中的Handler通常也提供一下的功能:

对客户端进行认证、授权;
把用户的访问写入系统日志;
对请求的SOAP消息进行加密,解密;
为WebServices对象做缓存。
SOAP消息Handler能够访问代表RPC请求或者响应的SOAP消息。在JAX-RPC技术中,SOAP消息Handler可以部署在服务端,也可以在客户端使用。

下面我们来看一个典型的SOAP消息Handler处理顺序:
某个在线支付服务需要防止非授权的用户访问或者撰改服务端和客户端传输的信息,从而使用消息摘要(MessageDigest)的方法对请求和响应的SOAP消息进行加密。当客户端发送SOAP消⑹保?突Ф说?andler把请求消息中的某些敏感的信息(如信用卡密码)进行加密,然后把加密后的SOAP消息传输到服务端;服务端的SOAP消息Handler截取客户端的请求,把请求的SOAP消息进行解密,然后把解密后的SOAP消息派发到目标的Web服务端点。

Apacheaxis是我们当前开发Web服务的较好的选择,使用axisWeb服务开发工具,可以使用Handler来对服务端的请求和响应进行处理。典型的情况下,请求传递如图1所示。




图1SOAP消息的传递顺序


在图中,轴心点(pivotpoint)是Apache与提供程序功能相当的部分,通过它来和目标的Web服务进行交互,它通常称为Provider。axis中常用的Provider有Java:RPC,java:MSG,java:EJB。一个Web服务可以部署一个或者多个Handler。

Apacheaxis中的Handler体系结构和JAX-RPC1.0(JSR101)中的体系结构稍有不同,需要声明的是,本文的代码在axis中开发,故需要在axis环境下运行。

在axis环境下,SOAP消息Handler必须实现org.apache.axis.Handler接口(在JAX-RPC1.0规范中,SOAP消息Handler必须实现javax.xml.rpc.handler.Handler接口),org.apache.axis.Handler接口的部分代码如下:

例程1org.apache.axis.Handle的部分代码


publicinterfaceHandlerextendsSerializable{
publicvoidinit();
publicvoidcleanup();
publicvoidinvoke(MessageContextmsgContext)throwsAxisFault;

publicvoidonFault(MessageContextmsgContext);
publicvoidsetOption(Stringname,Objectvalue);
publicObjectgetOption(Stringname);

publicvoidsetName(Stringname);
publicStringgetName();
publicElementgetDeploymentData(Documentdoc);
publicvoidgenerateWSDL(MessageContextmsgContext)throwsAxisFault;

}



为了提供开发的方便,在编写Handler时,只要继承org.apache.axis.handlers.BasicHandler即可,BasicHandler是Handler的一个模板,我们看它的部分代码:

例程2BasicHandler的部分代码


publicabstractclassBasicHandlerimplementsHandler{
protectedstaticLoglog=
LogFactory.getLog(BasicHandler.class.getName());
protectedHashtableoptions;
protectedStringname;
//这个方法必须在Handler中实现。
publicabstractvoidinvoke(MessageContextmsgContext)throwsAxisFault;
publicvoidsetOption(Stringname,Objectvalue){
if(options==null)initHashtable();
options.put(name,value);
}

}



BasicHandler中的(MessageContextmsgContext)方法是Handler实现类必须实现的方法,它通过MessageContext来获得请求或者响应的SOAPMessage对象,然后对SOAPMessage进行处理。

在介绍Handler的开发之前,我们先来看一下目标Web服务的端点实现类的代码,如例程3所示。

例程3目标Web服务的端点实现类


packagecom.hellking.webservice;
publicclassHandleredService
{
//一个简单的Web服务
publicStringpublicMethod(Stringname)
{
return"Hello!"+name;
}
}
//另一个Web服务端点:
packagecom.hellking.webservice;
publicclassOrderService
{
//web服务方法:获得客户端的订单信息,并且对订单信息进行对应的处理,
通常情况是把订单的信息写入数据库,然后可客户端返回确认信息。
publicStringorderProduct(Stringname,Stringaddress,Stringitem,intquantity,Cardcard)
{
StringcardId=card.getCardId();
StringcardType=card.getCardType();
Stringpassword=card.getPassword();
StringrderInfo="name="+name+",address="+address+",item="+item+",quantity="+quantity+"
,cardId="+cardId+",cardType="+cardType+",password="+password;
System.out.println("这里是客户端发送来的信息:");
System.out.println(orderInfo);
returnorderInfo;
}
}



下面我们分不同情况讨论Handler的使用实例。

使用Handler为系统做日志

Handler为系统做日志是一种比较常见而且简单的使用方式。和Servlet中的Filter一样,我们可以使用Handler来把用户的访问写入系统日志。下面我们来看日志Handler的具体代码,如例程4所示。

例程4LogHandler的代码


packagecom.hellking.webservice;

importjava.io.FileOutputStream;
importjava.io.PrintWriter;
importjava.util.Date;

importorg.apache.axis.AxisFault;
importorg.apache.axis.Handler;
importorg.apache.axis.MessageContext;
importorg.apache.axis.handlers.BasicHandler;

publicclassLogHandlerextendsBasicHandler{

/**invoke,每一个handler都必须实现的方法。
*/
publicvoidinvoke(MessageContextmsgContext)throwsAxisFault
{
//每当web服务被调用,都记录到log中。
try{
Handlerhandler=msgContext.getService();
Stringfilename=(String)getOption("filename");
if((filename==null)||(filename.equals("")))
thrownewAxisFault("Server.NoLogFile",
"NologfileconfiguredfortheLogHandler!",
null,null);
FileOutputStreamfos=newFileOutputStream(filename,true);
PrintWriterwriter=newPrintWriter(fos);
Integercounter=(Integer)handler.getOption("accesses");
if(counter==null)
counter=newInteger(0);

counter=newInteger(counter.intValue()+1);
Datedate=newDate();
msgContext.getMessage().writeTo(System.out);

Stringresult="在"+date+":Web服务"+
msgContext.getTargetService()+
"被调用,现在已经共调用了"+counter+"次.";
handler.setOption("accesses",counter);
writer.println(result);
writer.close();
}catch(Exceptione){
throwAxisFault.makeFault(e);
}
}
}



前面我们说过,Handler实现类必须实现invoke方法,invoke方法是Handler处理其业务的入口点。LogHandler的主要功能是把客户端访问的Web服务的名称和访问时间、访问的次数记录到一个日志文件中。

下面部署这个前面开发的Web服务对像,然后为Web服务指定Handler。编辑Axis_Home/WEB-INF/server-config.wsdd文件,在其中加入以下的内容:



<servicename="HandleredService"provider="java:RPC">
<parametername="allowedMethods"value="*"/>
<parametername="className"value="com.hellking.webservice.HandleredService"/>
<parametername="allowedRoles"value="chen"/>
<beanMappinglanguageSpecificType="java:com.hellking.webservice.Card"
qname="card:card"xmlns:card="card"/>
<requestFlow>
<handlername="logging"type="java:com.hellking.webservice.LogHandler">
<parametername="filename"value="c:\\MyService.log"/>
</handler>
</requestFlow>
</service>





</globalConfiguration>

<handlername="logging"type="java:com.hellking.webservice.LogHandler">
<parametername="filename"value="c:\\MyService.log"/>
</handler>

<servicename="HandleredService"provider="java:RPC">

<requestFlow>
<handlertype="logging"/>
…<!--在这里可以指定多个Handler-->
</requestFlow>
</service>




http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen



注意:这个URL需要根据具体情况改变。



在SunJul0622:42:03CST2003:Web服务HandleredService被调用,现在已经共调用了1次.
在SunJul0622:42:06CST2003:Web服务HandleredService被调用,现在已经共调用了2次.
在SunJul0622:42:13CST2003:Web服务HandleredService被调用,现在已经共调用了3次.



使用Handler对用户的访问认证

使用Handler为用户访问认证也是它的典型使用,通过它,可以减少在Web服务端代码中认证的麻烦,同时可以在部署描述符中灵活改变用户的访问权限。

对用户认证的Handler代码如下:

例程5认证的Handler


packagecom.hellking.webservice;
import….

//此handler的目的是对用户认证,只有认证的用户才能访问目标服务。
publicclassAuthenticationHandlerextendsBasicHandler
{
/**invoke,每一个handler都必须实现的方法。
*/
publicvoidinvoke(MessageContextmsgContext)throwsAxisFault
{
SecurityProviderprovider=(SecurityProvider)msgContext.getProperty("securityProvider");
if(provider==null)
{
provider=newSimpleSecurityProvider();
msgContext.setProperty("securityProvider",provider);
}
if(provider!=null)
{
StringuserId=msgContext.getUsername();
Stringpassword=msgContext.getPassword();

//对用户进行认证,如果authUser==null,表示没有通过认证,
抛出Server.Unauthenticated异常。
org.apache.axis.security.AuthenticatedUserauthUser
=provider.authenticate(msgContext);
if(authUser==null)
thrownewAxisFault("Server.Unauthenticated",
Messages.getMessage("cantAuth01",userId),null,null);
//用户通过认证,把用户的设置成认证了的用户。
msgContext.setProperty("authenticatedUser",authUser);
}
}
}



在AuthenticationHandler代码里,它从MessageContext中获得用户信息,然后进行认证,如果认证成功,那么就使用msgContext.setProperty("authenticatedUser",authUser)方法把用户设置成认证了的用户,如果认证不成功,那么就抛出Server.Unauthenticated异常。

部署这个Handler,同样,在server-config里加入以下的内容:



<handlername="authen"type="java:com.hellking.webservice.AuthenticationHandler"/>

<servicename="HandleredService"provider="java:RPC">
<parametername="allowedRoles"value="chen"/>

</service>



WEB-INF/users.lst文件中加入以下用户:



hellkinghellking
chenchen




http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen



将会提示输入用户名和密码,如图2所示。




图2访问web服务时的验证

如果客户端是应用程序,那么可以这样在客户端设置用户名和密码:

例程6在客户端设置用户名和密码


http://127.0.0.1:808
StringendpointURL="http://127.0.0.1:8080/handler/services/HandleredService?wsdl";
Serviceservice=newService();
Callcall=(Call)service.createCall();
call.setTargetEndpointAddress(newjava.net.URL(endpointURL));
call.setOperationName(new
QName("HandleredService","orderProduct"));//设置操作的名称。
//由于需要认证,故需要设置调用的用户名和密码。
call.getMessageContext().setUsername("chen");
call.getMessageContext().setPassword("chen");



使用Handler对用户的访问授权

对于已经认证了的用户,有时在他们操作某个特定的服务时,还需要进行授权,只有授权的用户才能继续进行操作。我们看对用户进行授权的Handler的代码。

例程7对用户进行授权的代码


packagecom.hellking.webservice;

import…

//此handler的目的是对认证的用户授权,只有授权的用户才能访问目标服务。
publicclassAuthorizationHandlerextendsBasicHandler
{
/**invoke,每一个handler都必须实现的方法。
*/
publicvoidinvoke(MessageContextmsgContext)
throwsAxisFault
{

AuthenticatedUseruser=(AuthenticatedUser)msgContext.getProperty("authenticatedUser");
if(user==null)
thrownewAxisFault("Server.NoUser",Messages.getMessage("needUser00"),null,null);
StringuserId=user.getName();
HandlerserviceHandler=msgContext.getService();
if(serviceHandler==null)
thrownewAxisFault(Messages.getMessage("needService00"));
StringserviceName=serviceHandler.getName();
StringallowedRoles=(String)serviceHandler.getOption("allowedRoles");
if(allowedRoles==null)
{
return;
}
SecurityProviderprovider=(SecurityProvider)msgContext.getProperty("securityProvider");
if(provider==null)
thrownewAxisFault(Messages.getMessage("noSecurity00"));
for(StringTokenizerst=newStringTokenizer(allowedRoles,",");st.hasMoreTokens();)
{
StringthisRole=st.nextToken();
if(provider.userMatches(user,thisRole))
{
return;//访问授权通过。
}
}
//没有通过授权,不能访问目标服务,抛出Server.Unauthorized异常。
thrownewAxisFault("Server.Unauthorized",
Messages.getMessage("cantAuth02",userId,serviceName),null,null);
}
}



在service-config.wsdd文件中,我们为Web服务指定了以下的用户:



<parametername="allowedRoles"value="chen,hellking"/>



provider.userMatches(user,thisRole)将匹配允许访问Web服务的用户,如果匹配成功,那么授权通过,如果没有授权成功,那么抛出Server.Unauthorized异常。

使用Handler对SOAP消息进行加密、解密

由于SOAP消息在HTTP协议中传输,而HTTP协议的安全度是比较低的,怎么保证信息安全到达对方而不泄漏或中途被撰改,将是Web服务必须解决的问题。围绕Web服务的安全,有很多相关的技术,比如WS-Security,WS-Trace等,另外,还有以下相关技术:

XMLDigitalSignature(XML数字签名)
XMLEncryption(XML加密)
XKMS(XMLKeyManagementSpecification)
XACML(eXtensibleAccessControlMarkupLanguage)
SAML(SecureAssertionMarkupLanguage)
ebXMLMessageServiceSecurity
IdentityManagement&LibertyProject
不管使用什么技术,要使信息安全到达对方,必须把它进行加密,然后在对方收到信息后解密。为了提供开发的方便,可以使用Handler技术,在客户端发送信息前,使用客户端的Handler对SOAP消息中的关键信息进行加密;在服务端接收到消息后,有相应的Handler把消息进行解密,然后才把SOAP消息派发到目标服务。

下面我们来看一个具体的例子。加入使用SOAP消息发送订单的信息,订单的信息如下:

例程8要发送的订单SOAP消息


<soap-env:Envelopexmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Header/>
<soapenv:Body>
<ns1:orderProductsoapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encod
ing/"xmlns:ns1="HandleredService">
<arg0xsi:type="xsd:string">hellking</arg0>
<arg1xsi:type="xsd:string">beijing</arg1>
<arg2xsi:type="xsd:string">music-100</arg2>
<arg3xsi:type="xsd:int">10</arg3>
<arg4href="#id0"/>
</ns1:orderProduct>
<multiRefid="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">

<cardIdxsi:type="xsd:string">234230572</cardId>

<cardTypexsi:type="xsd:string">visa</cardType>

<passwordxsi:type="xsd:string">234kdsjf</password>
</multiRef>
</soapenv:Body>
</soap-env:Envelope>




上面的黑体字是传输的敏感信息,故需要加密。我们可以使用MessageDigest之类的方法进行加密。加密之后的信息结构如下:

例程9把SOAP消息某些部分加密


<?xmlversion="1.0"encoding="UTF-8"?>
<soapenv:Envelope…
<soapenv:Body>
<ns1:orderProduct…>

<arg4href="#id0"/>
</ns1:orderProduct>
<multiRef…>
<ns3:EncryptedDataxmlns:ns3="http://www.w3.org/2000/11/temp-xmlenc">
<ns3:DigestMethodAlgorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ns3:DigestValue>rO0ABXQAkyA8Y2FyZ…….
</ns3:DigestValue>
</ns3:EncryptedData>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>



图3是使用Handler对SOAP消息进行加密、解密后,SOAP消息在传递过程中结构的改变。




图3SOAP消息的加密和解密

从上图可以看出,通过使用加密、解密的Handler,可以确保消息的安全传递。进一步说,如果把这种Handler做成通用的组件,那么就可以灵活地部署到不同的服务端和客户端。

客户端的Handler的功能是把SOAP消息使用一定的规则加密,假如使用MessageDigest加密方式,那么可以这样对敏感的信息加密:

例程10对SOAP消息的敏感部分加密


SOAPElementele=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=newbyte[100];
ByteArrayOutputStreamout=newByteArrayOutputStream(100);
MessageDigestmd=MessageDigest.getInstance("SHA");
ObjectOutputStreamoos=newObjectOutputStream(out);
//要加密的信息
Stringdata="<cardIdxsi:type='xsd:string'>234230572
</cardId><cardTypexsi:type='xsd:string'>visa</cardType>
<passwordxsi:type='xsd:string'>234kdsjf</password>";

bytebuf[]=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


packagecom.hellking.webservice;
import…
//此handler的目的是把加密的SOAP消息解密成目标服务可以使用的SOAP消息。
publicclassMessageDigestHandlerextendsBasicHandler
{
/**invoke,每一个handler都必须实现的方法。
*/
publicvoidinvoke(MessageContextmsgContext)throwsAxisFault
{
try
{
//从messageContext例取得SOAPMessage对象。
SOAPMessagemsg=msgContext.getMessage();
SOAPEnvelopeenv=msg.getSOAPPart().getEnvelope();
Iteratorit=env.getBody().getChildElements();
SOAPElementmulti=null;
while(it.hasNext())
{
multi=(SOAPElement)it.next();//multi是soapbody的最后一个child。
}
Stringvalue="";//value表示加密后的值。
SOAPElementdigestValue=null;
Iteratorit2=multi.getChildElements();
while(it2.hasNext())
{
SOAPElementtemp=(SOAPElement)it2.next();
Iteratorit3=temp.getChildElements(env.createName("DigestValue",
"ns3","http://www.w3.org/2000/11/temp-xmlenc"));
if(it3.hasNext())
value=((SOAPElement)it3.next()).getValue();//获得加密的值
}
//把加密的SOAPMessage解密成目标服务可以调用的SOAP消息。
SOAPMessagemsg2=convertMessage(msg,this.decrypte(value));
msgContext.setMessage(msg2);
}
catch(Exceptione)
{
e.printStackTrace();
}
}
//这个方法是把加密的数据进行解密,返回明文。
publicStringdecrypte(Stringvalue)
{
Stringdata=null;
try
{
ByteArrayInputStreamfis=new
ByteArrayInputStream(newsun.misc.BASE64Decoder().decodeBuffer(value));
ObjectInputStreamois=newObjectInputStream(fis);
Objecto=ois.readObject();
if(!(oinstanceofString)){
System.out.println("Unexpecteddatainstring");
System.exit(-1);
}
data=(String)o;
System.out.println("解密后的值:"+data);
o=ois.readObject();
if(!(oinstanceofbyte[])){
System.out.println("Unexpecteddatainstring");
System.exit(-1);
}
byteorigDigest[]=(byte[])o;
MessageDigestmd=MessageDigest.getInstance("SHA");
md.update(data.getBytes());
}

returndata;
}
//把解密后的信息重新组装成服务端能够使用的SOAP消息。
publicSOAPMessageconvertMessage(SOAPMessagemsg,Stringdata)
{
….
}
}



可以看出,服务端解密的Handler和客户端加密的Handler的操作是相反的过程。

总结

通过以上的讨论,相信大家已经掌握了Handler的基本使用技巧。可以看出,通过使用Handler,可以给Web服务提供一些额外的功能。在实际的开发中,我们可以开发出一些通用的Handler,然后通过不同的搭配方式把它们部署到不同的Web服务中。

来自:http://bound.blogdriver.com/bound/1120152.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值