J2EE Web服务开发系列之十三: 实现安全的AXIS Web服务,第2部分

阅读本文前您需要以下的知识和工具:

  • Apache axis1.1,并且会初步使用;
  • Tomcat 5.0.16以上, 并且会初步使用;
  • SOAP消息(SOAP Message)编程知识;
  • Java安全编程基础知识;
  • JAX-RPC编程基础知识;
  • Servlet的开发经验;
  • Sun提供的JAX-RPC参考实现(jaxrpc-impl.jar,在J2EESDK1.4或者JWSDP1.4中可找到);
  • 一个JSSE安全提供者(如ISNetworks);
  • Trust Services Integration Kit,可在http://www.xmltrustcenter.org上获得。

本文的参考资料见 参考资料

本文的全部代码在这里 下载

Web 服务安全性相关技术和开发工具

Web 服务安全性规范是一套可以帮助 Web 服务开发者保证 SOAP 消息交换的安全的机制。WS-Security 特别描述了对现有的 SOAP 消息传递的增强,从而通过对 SOAP 消息应用消息完整性、消息机密性和单消息认证提供了保护级别。这些基本机制可以通过各种方式联合,以适应构建使用多种加密技术的多种安全性模型。

围绕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

由于本文是一个实例性文章,故不对WS-Security做详细的探讨,你可以在develperWorks Web 服务安全专题找到许多相关资料(见参考资料)。

Trust Services Integration Kit提供了一个WS-Security实现。你可以从http://www.xmltrustcenter.org获得相关库文件,分别是wssecurity.jar和tsik.jar。wssecurity.jar中包含一个WSSecurity类,可以使用它来对XML进行数字签名和验证,加密与解密。

下面我们使用WS-Security来对SOAP消息进行数字签名,然后再进行验证。





回页首


SOAP消息的签名和验证

使用WSSecurity对SOAP消息数字签名

在对SOAP消息进行签名前,首先生成一个keystore。keystore包含了进行数字签名所需要的身份信息。通过以下批处理脚本来创建keystore:


例程1 创建keystore(server.keystore)

                        set SERVER_DN="CN=hellking-Server, OU=huayuan, O=huayuan, L=BEIJINGC, S=BEIJING, C=CN"
                        set KS_PASS=-storepass changeit
                        set KS_TYPE=-storetype JKS
                        set KEYINFO=-keyalg RSA
                        #生成服务器端keystore。
                        keytool -genkey -dname %SERVER_DN% %KS_PASS% %KS_TYPE% -keystore
                        server.keystore %KEYINFO% -keypass changeit
                        

SignAndVerifySoap类中包含了一个对XML进行签名的方法,它就是sign(),这个方法将对SOAP消息进行签名,然后输出和WS-Security兼容的SOAP消息。下面我们看具体代码。


例程2 对SOAP消息签名

                        	package com.hellking.study.webservice;
                        import com.verisign.messaging.WSSecurity;
                        ...
                        public class SignAndVerifySoap {
                        final String KEY_STORE = "server.keystore";
                        final String SOTE_PASS = "changeit";
                        final String KEY_ALIAS="mykey";
                        final String TARGET_FILE="signed.xml";//签名后的SOAP消息
                        final String SOURE_FILE="source.xml";//签名前的SOAP消息
                        final String KEY_TYPE="JKS";
                        /**
                        *对xml进行签名
                        */
                        public void sign()
                        {
                        try
                        {
                        System.out.println("开始对SOAP消息进行签名,使用的密匙库:" + KEY_STORE + "/n");
                        // 获得私有key和相关证书,请参考JAVA安全编程相关书籍
                        FileInputStream fileInputStream = new FileInputStream(KEY_STORE);
                        System.out.println(java.security.KeyStore.getDefaultType());
                        java.security.KeyStore store = java.security.KeyStore.getInstance(KEY_TYPE);
                        store.load(fileInputStream, SOTE_PASS.toCharArray());
                        PrivateKey key = (PrivateKey)store.getKey(KEY_ALIAS, SOTE_PASS.toCharArray());
                        X509Certificate certification = (X509Certificate)store.getCertificate(KEY_ALIAS);
                        // 读取XML源文件到文档中
                        Document source = readFile(SOURE_FILE);
                        SigningKey signingKey = SigningKeyFactory.makeSigningKey(key);
                        KeyInfo keyInfo = new KeyInfo();
                        keyInfo.setCertificate(certification);
                        WSSecurity wsSecurity = new WSSecurity();
                        wsSecurity.setPreferredNamespace("http://schemas.xmlsoap.org/ws/2003/06/secext");
                        //对SOAP消息进行签名
                        wsSecurity.sign(source, signingKey, keyInfo);
                        // 保存签名后的SOAP消息
                        writeFile(source, new FileOutputStream(TARGET_FILE));
                        System.out.println("把签名后的文件写入: " + TARGET_FILE + ",请查看结果!");
                        }
                        catch(Exception e)
                        {
                        e.printStackTrace();
                        }
                        }
                        

在执行此程序前,请把wssecurity.jar、source.xml和tsik.jar设置到类路径环境变量中。签名前的SOAP为:


例程3 签名前的SOAP消息(source.xml)

                        <?xml version="1.0" encoding="UTF-8"?>
                        <soapenv:Envelope
                        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                        <soapenv:Body>
                        <ns1:getTax soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                        xmlns:ns1="http://hellking.webservices.com/">
                        <op1 xsi:type="xsd:double">5000.0</op1>
                        </ns1:getTax>
                        </soapenv:Body>
                        </soapenv:Envelope>
                        

签名后的SOAP消息如例程4所示。


例程4 签名后的SOAP消息(signed.xml)

                        <?xml version="1.0" encoding="UTF-8"?>
                        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                        xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                        <soapenv:Header>
                        <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext">
                        <wsse:BinarySecurityToken EncodingType="wsse:Base64Binary"
                        ValueType="wsse:X509v3" wsu:Id="wsse-ee805a80-cd95-11d8-9cf9-fd6213c0f8be"
                        xmlns:wsu="http://schemas.xmlsoap.org/ws/2003/06/utility">MIICUjCCAbsCBEDB0GIwDQYJKoZIhvcNAQE…VkTkPw==
                        </wsse:BinarySecurityToken>
                        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                        <ds:SignedInfo>
                        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                        <ds:Reference URI="#wsse-ee5308f0-cd95-11d8-9cf9-fd6213c0f8be">
                        <ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        </ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <ds:DigestValue>ZjRVnI2g7kcX0h9r4JtiltpYQPA=</ds:DigestValue></ds:Reference>
                        <ds:Reference URI="#wsse-ee4e4e00-cd95-11d8-9cf9-fd6213c0f8be">
                        <ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        </ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <ds:DigestValue>moZ0d+8mH1kfNw0VEK39V0Td9EM=</ds:DigestValue>
                        </ds:Reference>
                        </ds:SignedInfo>
                        <ds:SignatureValue>fPpYrf0uNP8W2XVVIQNc3OQt2Wn90M/0uJ0dDZTNRR0NxBBBX36wSXt7NfI5Fmh4ru44Wk34EGI7mqMAE5O0
                        /wtIlFRJt3zAvA6k3nhgcYj6tn/9kZwwxh1RkFTfTX9xdQ6Xn+P6m+YBm1YEEcTWkJd7XcxdyDEns2kYOhONx1U=
                        </ds:SignatureValue>
                        <ds:KeyInfo><wsse:SecurityTokenReference>
                        <wsse:Reference URI="#wsse-ee805a80-cd95-11d8-9cf9-fd6213c0f8be"/>
                        </wsse:SecurityTokenReference>
                        </ds:KeyInfo>
                        </ds:Signature></wsse:Security>
                        <wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2003/06/utility">
                        <wsu:Created wsu:Id="wsse-ee4e4e00-cd95-11d8-9cf9-fd6213c0f8be">2004-07-04T08:41:23Z</wsu:Created>
                        </wsu:Timestamp></soapenv:Header>
                        <soapenv:Body wsu:Id="wsse-ee5308f0-cd95-11d8-9cf9-fd6213c0f8be"
                        xmlns:wsu="http://schemas.xmlsoap.org/ws/2003/06/utility">
                        <ns1:getTax soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                        xmlns:ns1="http://hellking.webservices.com/">
                        <op1 xsi:type="xsd:double">5000.0</op1>
                        </ns1:getTax>
                        </soapenv:Body>
                        </soapenv:Envelope>
                        

签名后的SOAP消息中,头部包含了签名信息以及验证SOAP消息所需要的key。<SignedInfo> </SignedInfo> 描述了已签署的消息内容。<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 指出了签名算法(Signature Method Algorithm)。这个算法被用来将规范算法的输出转换成签名值(Signature Value)。Key Info 元素包含的部分就是数字证书本身。

对签名的SOAP消息进行验证

对SOAP消息进行验证就是使用keystore的信息生成TrustVerifier对象,然后调用WSSecurity的verify方法进行验证。


例程5 验证签名后的SOAP消息

                        	/**
                        *验证已经签名的SOAP消息
                        */
                        public void verify()
                        {
                        try
                        {
                        System.out.println("开始检验SOAP消息,使用的密匙库:" + KEY_STORE + "/n");
                        // 获得私有key和相关证书,请参考JAVA安全编程相关书籍
                        FileInputStream fileInputStream = new FileInputStream(KEY_STORE);
                        java.security.KeyStore store = java.security.KeyStore.getInstance(KEY_TYPE);
                        store.load(fileInputStream, SOTE_PASS.toCharArray());
                        // 读取XML源文件到文档中
                        Document source = readFile(TARGET_FILE);
                        org.xmltrustcenter.verifier.TrustVerifier verifier =
                        new org.xmltrustcenter.verifier.X509TrustVerifier(store);
                        WSSecurity wsSecurity = new WSSecurity();
                        com.verisign.messaging.MessageValidity[] resa =
                        wsSecurity.verify(source,verifier,null,null);
                        System.out.println("检验结果:");
                        for (int len = 0; len < resa.length; len++){
                        System.out.println("result[" + len + "] = " + (resa[len].isValid()?"验证通过":"验证不通过"));
                        }
                        }
                        catch(Exception e)
                        {
                        e.printStackTrace();
                        }
                        }
                        

执行SignAndVerifySoap的verify方法,可以看到类似以下的结果。


图1 对SOAP消息进行验证
图1 对SOAP消息进行验证




回页首


在AXIS下实现WS-Security的应用框架

待开发的应用开发框架基于Handler实现,将达到以下目标:此框架基于JAX-RPC环境下实现WS-Security应用,它可以部署到任何需要实现WS-Security的axis环境下的Web服务应用中,同时具体的应用程序不做任何编码修改。

由于此基于Handler实现,我们有必要回顾一下Handler的一些基础知识。

SOAP消息Handler能够访问代表RPC请求或者响应的SOAP消息。在JAX-RPC技术中,SOAP消息Handler可以部署在服务端,也可以在客户端使用。

SOAP消息Handler非常像Servlet技术中的Filter,它们共同的特点是请求发送到目标前,Handler/Filter可以截取这些请求,并对请求做一些处理,从而达到一些辅助的功能。多个Handler可以组成一个Handler链,链上的每个Handler都完成某个特定的任务。比如有的Handler进行权限验证,有的Handler进行日志处理等。关于Handler更详细的介绍,请参考本系列文章《 J2EE Web服务开发系列之六: 使用Handler来增强Web服务的功能》。

实现原理

图2是此例子具体实现原理图。


图2 Handler结合WSSecurity实现Web服务安全的工作原理
图2 Handler结合WSSecurity实现Web服务安全的工作原理

处理流程如下:

1、 客户端(WSSClient)发出调用Web服务请求;

2、 客户端Handler(WSSecurityClientHandler)截获请求的SOAP消息;

3、 客户端Handler对截获的SOAP消息进行数字签名(使用client.keystore作为签名依据);

4、 客户端Handler对签名后的SOAP消息进行加密(使用RSA算法加密);

5、 被加密的SOAP消息通过互联网传送到目标Web服务端口;

6、 服务器端Handler(WSSecurityServerHandler)截获加密的SOAP消息;

7、 服务器端Handler对加密的SOAP消息进行解密;

8、 服务器端Handler对SOAP消息进行身份验证(server.truststore包含了所信任的身份信息),如果验证不通过,将抛出异常;

9、 服务器端Handler删除被解密后的SOAP消息中与WS-Security相关的元素;

10、 解密后的原始SOAP消息被发送到目标Web服务端口(如TaxService);

11、 目标Web服务对Web服务请求进行处理,然后返回响应的SOAP消息;

12、 服务器端Handler截获响应的SOAP消息;

13、 服务器端Handler对截获的SOAP消息进行数字签名(使用server.keystore作为签名依据);

14、 服务器端Handler对签名后的SOAP消息进行加密(使用RSA算法加密);

15、 被加密的SOAP消息通过互联网传送到目客户端;

16、 客户端Handler截获加密的SOAP消息;

17、 客户端Handler对加密的SOAP消息进行解密;

18、 客户端Handler对SOAP消息进行身份验证(client.truststore包含了所信任的身份信息),如果验证不通过,将抛出异常;

19、 客户端Handler删除被解密后的SOAP消息中与WS-Security相关的元素;

20、 被解密后的SOAP消息发送到目标客户端,客户端输出调用结果。

从上面可以看出,在一个SOAP调用回合中,要对SOAP消息进行四次处理。基本上都是"签名'加密'解密'验证"的过程。

创建相关密匙库

客户端和服务端都有相关的密匙库,其中:

  • client.keystore:客户端自身的身份信息;
  • client.truststore:客户端所信任的身份信息,在此例中也就是包含了服务器的身份信息;
  • server.keystore:服务器自身的身份信息;
  • server.truststore:服务器所信任的身份信息(即客户端身份信息)。

你可以使用以下的批处理脚本创建上面四个密匙库。


例程6 创建相关密匙库(gen-cer-store.bat)

                        set SERVER_DN="CN=hellking-Server, OU=huayuan, O=huayuan, L=BEIJINGC, S=BEIJING, C=CN"
                        set CLIENT_DN="CN=hellking-Client, OU=tsinghua, O=tsinghua, L=BEIJING, S=BEIJING, C=CN"
                        set KS_PASS=-storepass changeit
                        set KEYINFO=-keyalg RSA
                        #生成server.keystore。
                        keytool -genkey -dname %SERVER_DN% %KS_PASS% -keystore server.keystore %KEYINFO% -keypass changeit
                        #从server.keystore导出数字证书。
                        keytool -export -file test_axis.cer %KS_PASS% -keystore server.keystore
                        #从服务器的数字证书导出到客户端信任的truststore中。
                        keytool -import -file test_axis.cer %KS_PASS% -keystore client.truststore -alias serverkey -noprompt
                        #生成client.keystore。
                        keytool -genkey -dname %CLIENT_DN% %KS_PASS% -keystore client.keystore %KEYINFO% -keypass changeit
                        #从client.keystore导出数字证书。
                        keytool -export -file test_axis.cer %KS_PASS% -keystore client.keystore
                        #从客户端的数字证书导出到服务器信任的truststore中。
                        keytool -import -file test_axis.cer %KS_PASS% -keystore server.truststore -alias clientkey -noprompt
                        #end
                        

签名、加密、解密、身份验证的实现

对SOAP消息的签名、加密、解密、身份验证都放在一个名为WSSHelper的类中进行。


例程7 签名、加密、解密、身份验证功能的实现――WSSHelper.java

                        package com.hellking.study.webservice;
                        import com.verisign.messaging.WSSecurity;
                        ...
                        public class WSSHelper {
                        static String PROVIDER="ISNetworks";//JSSE安全提供者。
                        //添加JSSE安全提供者,你也可以使用其它安全提供者。只要支持DESede算法。
                        static
                        {
                        java.security.Security.addProvider(new com.isnetworks.provider.jce.ISNetworksProvider());
                        }
                        /**
                        *对XML文档进行数字签名。
                        */
                        public static void sign(Document doc, String keystore, String storetype,
                        String storepass, String alias, String keypass) throws Exception {
                        FileInputStream fileInputStream = new FileInputStream(keystore);
                        java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype);
                        keyStore.load(fileInputStream, storepass.toCharArray());
                        PrivateKey key = (PrivateKey)keyStore.getKey(alias, keypass.toCharArray());
                        X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias);
                        SigningKey sk = SigningKeyFactory.makeSigningKey(key);
                        KeyInfo ki = new KeyInfo();
                        ki.setCertificate(cert);
                        WSSecurity wSSecurity = new WSSecurity();
                        wSSecurity.sign(doc, sk, ki);//签名。
                        }
                        /**
                        *对XML文档进行身份验证。
                        */
                        public static boolean verify(Document doc, String keystore, String storetype,
                        String storepass) throws Exception {
                        FileInputStream fileInputStream = new FileInputStream(keystore);
                        java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype);
                        keyStore.load(fileInputStream, storepass.toCharArray());
                        TrustVerifier verifier = new X509TrustVerifier(keyStore);
                        WSSecurity wSSecurity = new WSSecurity();
                        MessageValidity[] resa = wSSecurity.verify(doc, verifier, null,null);
                        if (resa.length > 0)
                        return resa[0].isValid();
                        return false;
                        }
                        /**
                        *对XML文档进行加密。必须有JSSE提供者才能加密。
                        */
                        public static void encrypt(Document doc, String keystore, String storetype,
                        String storepass, String alias) throws Exception {
                        try
                        {
                        FileInputStream fileInputStream = new FileInputStream(keystore);
                        java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype);
                        keyStore.load(fileInputStream, storepass.toCharArray());
                        X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias);
                        PublicKey pubk = cert.getPublicKey();
                        KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede",PROVIDER);
                        keyGenerator.init(168, new SecureRandom());
                        SecretKey key = keyGenerator.generateKey();
                        KeyInfo ki = new KeyInfo();
                        ki.setCertificate(cert);
                        WSSecurity wSSecurity = new WSSecurity();
                        //加密。
                        wSSecurity.encrypt(doc, key, AlgorithmType.TRIPLEDES, pubk, AlgorithmType.RSA1_5, ki);
                        }
                        catch(Exception e)
                        {
                        e.printStackTrace();
                        }
                        }
                        /**
                        *对文档进行解密。
                        */
                        public static void decrypt(Document doc, String keystore, String storetype,
                        String storepass, String alias, String keypass) throws Exception {
                        FileInputStream fileInputStream = new FileInputStream(keystore);
                        java.security.KeyStore keyStore = java.security.KeyStore.getInstance(storetype);
                        keyStore.load(fileInputStream, storepass.toCharArray());
                        PrivateKey prvk2 = (PrivateKey)keyStore.getKey(alias, keypass.toCharArray());
                        WSSecurity wSSecurity = new WSSecurity();
                        //解密。
                        wSSecurity.decrypt(doc, prvk2, null);
                        WsUtils.removeEncryptedKey(doc);//从 WS-Security Header中删除 EncryptedKey 元素
                        }
                        public static void removeWSSElements(Document doc) throws Exception {
                        WsUtils.removeWSSElements(doc);// 删除WSS相关的元素。
                        }
                        }
                        

WSSHelper类中使用了ISNetworks安全提供者,ISNetworks实现了RSA加密、解密算法。当然,你也可以使用其它的安全提供者,并且可以使用不同的加密算法。可以从网络上下载ISNetworks相关包。

WSSHelper中包含了一个WsUtils类,它的功能就是从加密后的SOAP消息中删除一些WS-Security元素,删除这些元素后的SOAP消息才能被最终的客户端或者Web服务端处理。

服务器端Handler开发

当请求到达后,服务端Handler调用handleRequest方法,执行如下过程:对请求SOAP消息解密'身份验证'删除WSS元素'把Document转换成SOAP消息。 Web服务端点对请求做出响应后,将调用handleResponse方法,执行如下过程:对响应的SOAP消息进行数字签名'加密'把Document转换成SOAP消息。


例程8 服务器端Handler(WSSecurityServerHandler.java)

                        package com.hellking.study.webservice;
                        ...
                        //服务器端Handler
                        public class WSSecurityServerHandler implements Handler
                        {
                        //密匙库相关信息
                        private String keyStoreFile = null;
                        private String keyStoreType = "JKS";
                        。。。
                        public WSSecurityServerHandler()
                        {
                        System.out.println("服务端Handler:构造方法");
                        }
                        /**
                        *处理请求
                        *流程:解密-->身份验证-->删除WSS元素'把Document转换成SOAP消息。
                        */
                        public boolean handleRequest(MessageContext messageContext) {
                        System.out.println("开始处理请求。。。");
                        if (messageContext instanceof SOAPMessageContext){
                        try {
                        SOAPMessageContext soapMessageContext = (SOAPMessageContext)messageContext;
                        SOAPMessage soapMessage = soapMessageContext.getMessage();
                        soapMessage.writeTo(System.out);
                        Document doc = MessageConveter.convertSoapMessageToDocument(soapMessage);
                        //解密
                        WSSHelper.decrypt(doc, keyStoreFile, keyStoreType,
                        keyStorePassword, keyAlias, keyEntryPassword);
                        //身份验证
                        WSSHelper.verify(doc, trustStoreFile, trustStoreType, trustStorePassword);
                        //删除WSS元素
                        WSSHelper.removeWSSElements(doc);
                        soapMessage = MessageConveter.convertDocumentToSOAPMessage(doc);
                        soapMessageContext.setMessage(soapMessage);
                        } catch (Exception e){
                        System.err.println("在处理请求时发生了异常: " + e);
                        e.printStackTrace();
                        return false;
                        }
                        } else {
                        System.out.println("MessageContext是以下类的实例: " + messageContext.getClass());
                        }
                        System.out.println("处理请求完毕!");
                        return true;
                        }
                        /**
                        *处理响应
                        *流程:数字签名-->加密-->把Document转换成SOAP消息。
                        */
                        public boolean handleResponse(MessageContext messageContext) {
                        System.out.println("开始处理Web服务响应。。。");
                        if (messageContext instanceof SOAPMessageContext){
                        try {
                        SOAPMessageContext soapMessageContext = (SOAPMessageContext)messageContext;
                        SOAPMessage soapMessage = soapMessageContext.getMessage();
                        Document doc = MessageConveter.convertSoapMessageToDocument(soapMessage);
                        WSSHelper.sign(doc, keyStoreFile, keyStoreType,
                        keyStorePassword, keyAlias, keyEntryPassword);
                        WSSHelper.encrypt(doc, trustStoreFile, trustStoreType,
                        trustStorePassword, certAlias);
                        soapMessage = MessageConveter.convertDocumentToSOAPMessage(doc);
                        soapMessageContext.setMessage(soapMessage);
                        } catch (Exception e){
                        System.err.println("在处理响应时发生以下错误: " + e);
                        e.printStackTrace();
                        return false;
                        }
                        }
                        System.out.println("处理响应完毕!");
                        return true;
                        }
                        /**
                        *初始化,主要是初始化一些相关参数。
                        */
                        public void init(HandlerInfo config) {
                        System.out.println("WSSecurityServerHandler初始化");
                        Object param = "";
                        Map configs = config.getHandlerConfig();
                        keyStoreFile = (String)configs.get("keyStoreFile");
                        trustStoreFile = (String)configs.get("trustStoreFile");
                        …//其它参数初始化
                        }
                        …
                        }
                        

客户端Handler开发

客户端Handler可以是任何JAX-RPC兼容的Handler处理器。比如AXIS Handler实现或者SUN 提供的JAX-RPC Handler参考实现。这里使用后者来作为客户端Handler处理器。

客户端Handler和服务器端Handler原理一样,但处理过程完全相反。


例程9 客户端Handler(WSSecurityClientHandler.java)

                        package com.hellking.study.webservice;
                        …
                        //客户端Handler
                        public class WSSecurityClientHandler implements Handler
                        {
                        //密匙库相关信息
                        ...
                        /**
                        *处理请求
                        *流程:数字签名-->加密-->把Document转换成SOAP消息。
                        */
                        public boolean handleRequest(MessageContext messageContext) {
                        System.out.println("开始处理请求。。。");
                        …
                        WSSHelper.sign(doc, keyStoreFile, keyStoreType,
                        keyStorePassword, keyAlias, keyEntryPassword);
                        WSSHelper.encrypt(doc, trustStoreFile, trustStoreType,
                        trustStorePassword, certAlias);
                        soapMessage = MessageConveter.convertDocumentToSOAPMessage(doc);
                        soapMessageContext.setMessage(soapMessage);
                        …
                        System.out.println("处理请求完毕!");
                        return true;
                        }
                        /**
                        *处理响应
                        *流程:解密-->身份验证-->删除WSS元素'把Document转换成SOAP消息。
                        */
                        public boolean handleResponse(MessageContext messageContext) {
                        System.out.println("开始处理Web服务响应。。。");
                        …
                        WSSHelper.decrypt(doc, keyStoreFile, keyStoreType,
                        keyStorePassword, keyAlias, keyEntryPassword);
                        WSSHelper.verify(doc, trustStoreFile, trustStoreType, trustStorePassword);
                        WSSHelper.removeWSSElements(doc);
                        soapMessage = MessageConveter.convertDocumentToSOAPMessage(doc);
                        System.out.println("the final message is:");
                        soapMessage.writeTo(System.out);
                        soapMessageContext.setMessage(soapMessage);
                        …
                        System.out.println("处理响应完毕!");
                        return true;
                        }
                        /**
                        *初始化,主要是初始化一些相关参数。
                        */
                        public void init(HandlerInfo config) {
                        …
                        }
                        …
                        }
                        

部署服务器端Handler

为了使用Handler,需要在Web服务部署描述符中指定使用此Handler。Handler包含的初始化参数也在此描述,如例程10所示。


例程10 服务器端Handler部署代码

                          <service name="PersonalTaxServicePort" provider="java:RPC">
                        <parameter name="allowedMethods" value="*"/>
                        <parameter name="className" value="com.hellking.study.webservice.PersonalTaxService"/>
                        <parameter name="wsdlTargetNamespace" value="http://hellking.webservices.com/"/>
                        <parameter name="wsdlServiceElement" value="PersonalTaxService"/>
                        <parameter name="wsdlServicePort" value="PersonalTaxServicePort"/>
                        <parameter name="wsdlPortType" value="PersonalTaxService"/>
                        <requestFlow>
                        <handler type="java:org.apache.axis.handlers.JAXRPCHandler">
                        <parameter name="scope" value="session"/>
                        <parameter name="className"
                        value="com.hellking.study.webservice.WSSecurityServerHandler"/>
                        <parameter name="keyStoreFile"
                        value="K://jakarta-tomcat-5.0.16//server.keystore"/>
                        <parameter name="trustStoreFile"
                        value="K://jakarta-tomcat-5.0.16//server.truststore"/>
                        <parameter name="certAlias" value="clientkey"/>
                        </handler>
                        </requestFlow>
                        <responseFlow>
                        <handler type="java:org.apache.axis.handlers.JAXRPCHandler">
                        <parameter name="scope" value="session"/>
                        <parameter name="className"
                        value="com.hellking.study.webservice.WSSecurityServerHandler"/>
                        <parameter name="keyStoreFile"
                        value="K://jakarta-tomcat-5.0.16//server.keystore"/>
                        <parameter name="trustStoreFile"
                        value="K://jakarta-tomcat-5.0.16//server.truststore"/>
                        <parameter name="certAlias" value="clientkey"/>
                        </handler>
                        </responseFlow>
                        </service>
                        

requestFlow表示Web服务PersonalTaxServicePort的请求处理Handler链。这里只有一个Handler,就是WSSecurityServerHandler。当Web服务请求到达PersonalTaxServicePort时,WSSecurityServerHandler的handleRequest方法将被自动调用。

注意:部署时,请改变Handler相关参数以和目标的Web服务一致,比如trustStoreFile的路径等。

调用测试

这里采用代理的方式来调用Web服务,先编写一个Web服务接口。


例程11 TaxServiceInterface

                        package com.hellking.study.webservice;
                        …
                        /**
                        *个人所得税Web服务。
                        */
                        public interface TaxServiceInterface extends Remote
                        {
                        public double getTax(double salary)throws java.rmi.RemoteException;
                        }
                        

WSSClient客户端程序是通过代理的方式来访问Web服务的。由于要使用Handler,所以在访问前通过registerHandlers()方法注册了WSSecurityClientHandler,并且初始化了WSSecurityClientHandler的相关参数。当然,JAX-RPC"参考实现"还支持在Web服务客户端配置文件中描述Handler信息,这样就不需要在客户端代码中对Handler进行注册了,你可以参考相关文档。


例程12 测试客户端程序(WSSClient)

                        package com.hellking.study.webservice;
                        ...
                        /**
                        *调用需要验证的Web服务
                        */
                        public class WSSClient
                        {
                        static	final double salary=5000;
                        public static void main(String [] args)
                        {
                        try {
                        //服务端的url,需要根据情况更改。
                        String endpointURL = "http://localhost:8080/axis/services/PersonalTaxServicePort";
                        String wsdlURL=endpointURL+"?wsdl";
                        java.net.URL targetURL= new java.net.URL(wsdlURL);
                        String nameSpaceUri = "http://hellking.webservices.com/";
                        String svcName = "PersonalTaxService";
                        String portName = "PersonalTaxServicePort";
                        ServiceFactory svcFactory = ServiceFactory.newInstance();
                        Service svc = svcFactory.createService(targetURL, new QName(nameSpaceUri, svcName));
                        //cfg表示客户端的配置信息。
                        java.util.HashMap cfg = new java.util.HashMap();
                        cfg.put("keyStoreFile", "client.keystore");
                        cfg.put("trustStoreFile", "client.truststore");
                        cfg.put("certAlias", "changeit");
                        Class hdlrClass = com.hellking.study.webservice.WSSecurityClientHandler.class;
                        java.util.List list = svc.getHandlerRegistry().
                        getHandlerChain(new QName(nameSpaceUri, portName));
                        list.add(new javax.xml.rpc.handler.HandlerInfo(hdlrClass, cfg, null));
                        registerHandlers (svc);
                        TaxServiceInterface myProxy =
                        ( TaxServiceInterface) svc.getPort(new QName(nameSpaceUri, portName),
                        TaxServiceInterface.class);
                        double ret=myProxy.getTax(5000);
                        System.out.println("使用HTTP协议来作为Web服务的传输协议!");
                        System.out.println("已经成功调用。请参看服务端的输出!");
                        System.out.println("输入工资"+salary+"元,应交个人所得税:"+ret);
                        } catch (Exception e) {
                        e.printStackTrace();
                        }
                        }
                        //注册Handler
                        private static void registerHandlers ( Service service )
                        throws javax.xml.rpc.ServiceException {
                        java.util.HashMap cfg = new java.util.HashMap();
                        cfg.put("keyStoreFile", "client.keystore");
                        cfg.put("trustStoreFile", "client.truststore");
                        cfg.put("certAlias", "changeit");
                        /*
                        * 封装客户端Handler到HandlerInfo 中,然后添加到Handler链中。
                        */
                        javax.xml.rpc.handler.HandlerInfo info = new javax.xml.rpc.handler.HandlerInfo
                        (com.hellking.study.webservice.WSSecurityClientHandler.class, cfg, null );
                        java.util.ArrayList handlerList = new java.util.ArrayList();
                        handlerList.add(info);
                        /*
                        * 获得Handler注册
                        */
                        javax.xml.rpc.handler.HandlerRegistry handlerRegistry = service.getHandlerRegistry();
                        /*
                        * 把Handler添加到所有的port中。
                        */
                        java.util.Iterator portIterator = service.getPorts();
                        while ( portIterator.hasNext()) {
                        Object obj=portIterator.next();
                        QName portName = (QName) obj;
                        handlerRegistry.setHandlerChain(portName, handlerList);
                        }
                        }
                        }
                        

注意:由于客户端使用了SUN公司提供的"JAX-RPC参考实现",所以必须把jaxrpc-impl.jar包设置在CLASSPATH环境变量中,并且不要把axis.jar设置在客户端CLASSPATH环境变量,否则会出现ClassCastException异常。这是因为axis也是JAX-RPC的实现,如果它在CLASSPATH环境变量中,当调用:

ServiceFactory svcFactory = ServiceFactory.newInstance()方法时,就可能初始化一个axis的ServiceFactory 实现。

本文源代码中client目录下wss-client.bat文件包含了执行WSSClient脚本,修改了部分环境变量参数后,才能执行。





回页首


总结

本文和上一篇文章介绍了几种不同实现Web服务安全的方法,你可以根据具体应用对安全的要求级别采用不同的方式。对于安全级别要求不高的应用,可以采用在Web服务器上使用基本认证、使用Axis的Handler或者使用Servlet过滤器来实现访问控制;对于安全要求高的应用,可以采用本篇介绍开发的"axis下实现WS-Security的通用应用框架"来实现安全性。





回页首


参考资料





回页首


关于作者

陈亚强:北京华园天一科技有限公司高级软件工程师,擅长J2EE技术,曾参与多个J2EE项目的设计和开发,对Web服务有很大的兴趣并且有一定的项目经验。热爱学习,喜欢新技术,曾参与多本图书的写作。好交朋友,您可以通过 cyqcims@mail.tsinghua.edu.cn和他联系。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值