AXIS第三课:AXIS高级应用,使用Handler来增强web服务的功能
前面我们说过,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
将会提示输入用户名和密码。
访问web服务时的验证,如果客户端是应用程序,那么可以这样在客户端设置用户名和密码:
例程6 在客户端设置用户名和密码
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>
使用Handler对SOAP消息进行加密、解密后,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代码如下:
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服务中。