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

原文作者: 陈亚强

原文链接:http://www.ibm.com/developerworks/cn/webservices/ws-handler/index.html
高级软件工程师北京华园天一科技有限公司 2003  8  

一、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服务进行交互,它通常称为Provideraxis中常用的ProviderJavaRPCjavaMSGjavaEJB。一个Web服务可以部署一个或者多个Handler 

Apache axis
中的Handler体系结构和JAX-RPC 1.0JSR101)中的体系结构稍有不同,需要声明的是,本文的代码在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的部分代码
为了提供开发的方便,在编写Handler时,只要继承org.apache.axis.handlers. BasicHandler即可,BasicHandlerHandler的一个模板,我们看它的部分代码: 

例程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 所示。  

例程 目标 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 代码如下:

例程 认证的 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 所示。

<!--[if !vml]--><!--[endif]-->
 

访问 web 服务时的验证  

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

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

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 的代码。  

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

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 消息发送订单的信息,订单的信息如下:

例程 要发送的订单 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 >
     

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

例程 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 消息在传递过程中结构的改变。  

<!--[if !vml]--><!--[endif]-->
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 服务中。 

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

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值