posts - 495,comments - 227,trackbacks - 0
          高級軟件工程師
          2004 年 8 月
          本文將首先簡單介紹Web 服務安全性相關技術和開發(fā)工具,然后介紹了使用WSSecurity工具SOAP消息進行簽名和驗證的方法;接下來深入討論了使用現(xiàn)成的WS-Security工具,結合Handler模型開發(fā)一個axis下實現(xiàn)WS-Security的通用應用框架。

          本文是J2EE Web服務開發(fā)系列文章的第十三篇,本文將首先簡單介紹Web 服務安全性相關技術和開發(fā)工具,然后介紹了使用WSSecurity工具SOAP消息進行簽名和驗證的方法;接下來深入討論了使用現(xiàn)成的WS-Security工具,結合Handler模型開發(fā)一個axis下實現(xiàn)WS-Security的通用應用框架。

          閱讀本文前您需要以下的知識和工具:

          • Apache axis1.1,并且會初步使用;
          • Tomcat 5.0.16以上, 并且會初步使用;
          • SOAP消息(SOAP Message)編程知識;
          • Java安全編程基礎知識;
          • JAX-RPC編程基礎知識;
          • Servlet的開發(fā)經(jīng)驗;
          • Sun提供的JAX-RPC參考實現(xiàn)(jaxrpc-impl.jar,在J2EESDK1.4或者JWSDP1.4中可找到);
          • 一個JSSE安全提供者(如ISNetworks);
          • Trust Services Integration Kit,可在http://www.xmltrustcenter.org上獲得。

          本文的參考資料見 參考資料

          本文的全部代碼在這里 下載

          Web 服務安全性相關技術和開發(fā)工具
          Web 服務安全性規(guī)范是一套可以幫助 Web 服務開發(fā)者保證 SOAP 消息交換的安全的機制。WS-Security 特別描述了對現(xiàn)有的 SOAP 消息傳遞的增強,從而通過對 SOAP 消息應用消息完整性、消息機密性和單消息認證提供了保護級別。這些基本機制可以通過各種方式聯(lián)合,以適應構建使用多種加密技術的多種安全性模型。

          圍繞Web服務的安全,有很多相關的技術,比如WS-Security,WS-Trace等,另外,還有以下相關技術:

          • 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

          由于本文是一個實例性文章,故不對WS-Security做詳細的探討,你可以在develperWorks Web 服務安全專題找到許多相關資料(見參考資料)。

          Trust Services Integration Kit提供了一個WS-Security實現(xiàn)。你可以從http://www.xmltrustcenter.org獲得相關庫文件,分別是wssecurity.jar和tsik.jar。wssecurity.jar中包含一個WSSecurity類,可以使用它來對XML進行數(shù)字簽名和驗證,加密與解密。

          下面我們使用WS-Security來對SOAP消息進行數(shù)字簽名,然后再進行驗證。

          SOAP消息的簽名和驗證

          使用WSSecurity對SOAP消息數(shù)字簽名
          在對SOAP消息進行簽名前,首先生成一個keystore。keystore包含了進行數(shù)字簽名所需要的身份信息。通過以下批處理腳本來創(chuàng)建keystore:

          例程1 創(chuàng)建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();
          	}		
          	}
          	

          在執(zhí)行此程序前,請把wssecurity.jar、source.xml和tsik.jar設置到類路徑環(huán)境變量中。簽名前的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)。這個算法被用來將規(guī)范算法的輸出轉換成簽名值(Signature Value)。Key Info 元素包含的部分就是數(shù)字證書本身。

          對簽名的SOAP消息進行驗證
          對SOAP消息進行驗證就是使用keystore的信息生成TrustVerifier對象,然后調用WSSecurity的verify方法進行驗證。

          例程5 驗證簽名后的SOAP消息
          
          	/**
          	 *驗證已經(jīng)簽名的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();
          	}
          }
          

          執(zhí)行SignAndVerifySoap的verify方法,可以看到類似以下的結果。

          圖1 對SOAP消息進行驗證
          圖1 對SOAP消息進行驗證

          在AXIS下實現(xiàn)WS-Security的應用框架
          待開發(fā)的應用開發(fā)框架基于Handler實現(xiàn),將達到以下目標:此框架基于JAX-RPC環(huán)境下實現(xiàn)WS-Security應用,它可以部署到任何需要實現(xiàn)WS-Security的axis環(huán)境下的Web服務應用中,同時具體的應用程序不做任何編碼修改。

          由于此基于Handler實現(xiàn),我們有必要回顧一下Handler的一些基礎知識。

          SOAP消息Handler能夠訪問代表RPC請求或者響應的SOAP消息。在JAX-RPC技術中,SOAP消息Handler可以部署在服務端,也可以在客戶端使用。

          SOAP消息Handler非常像Servlet技術中的Filter,它們共同的特點是請求發(fā)送到目標前,Handler/Filter可以截取這些請求,并對請求做一些處理,從而達到一些輔助的功能。多個Handler可以組成一個Handler鏈,鏈上的每個Handler都完成某個特定的任務。比如有的Handler進行權限驗證,有的Handler進行日志處理等。關于Handler更詳細的介紹,請參考本系列文章《 J2EE Web服務開發(fā)系列之六: 使用Handler來增強Web服務的功能》。

          實現(xiàn)原理
          圖2是此例子具體實現(xiàn)原理圖。

          圖2 Handler結合WSSecurity實現(xiàn)Web服務安全的工作原理
          圖2 Handler結合WSSecurity實現(xiàn)Web服務安全的工作原理

          處理流程如下:

          1、 客戶端(WSSClient)發(fā)出調用Web服務請求;

          2、 客戶端Handler(WSSecurityClientHandler)截獲請求的SOAP消息;

          3、 客戶端Handler對截獲的SOAP消息進行數(shù)字簽名(使用client.keystore作為簽名依據(jù));

          4、 客戶端Handler對簽名后的SOAP消息進行加密(使用RSA算法加密);

          5、 被加密的SOAP消息通過互聯(lián)網(wǎng)傳送到目標Web服務端口;

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

          7、 服務器端Handler對加密的SOAP消息進行解密;

          8、 服務器端Handler對SOAP消息進行身份驗證(server.truststore包含了所信任的身份信息),如果驗證不通過,將拋出異常;

          9、 服務器端Handler刪除被解密后的SOAP消息中與WS-Security相關的元素;

          10、 解密后的原始SOAP消息被發(fā)送到目標Web服務端口(如TaxService);

          11、 目標Web服務對Web服務請求進行處理,然后返回響應的SOAP消息;

          12、 服務器端Handler截獲響應的SOAP消息;

          13、 服務器端Handler對截獲的SOAP消息進行數(shù)字簽名(使用server.keystore作為簽名依據(jù));

          14、 服務器端Handler對簽名后的SOAP消息進行加密(使用RSA算法加密);

          15、 被加密的SOAP消息通過互聯(lián)網(wǎng)傳送到目客戶端;

          16、 客戶端Handler截獲加密的SOAP消息;

          17、 客戶端Handler對加密的SOAP消息進行解密;

          18、 客戶端Handler對SOAP消息進行身份驗證(client.truststore包含了所信任的身份信息),如果驗證不通過,將拋出異常;

          19、 客戶端Handler刪除被解密后的SOAP消息中與WS-Security相關的元素;

          20、 被解密后的SOAP消息發(fā)送到目標客戶端,客戶端輸出調用結果。

          從上面可以看出,在一個SOAP調用回合中,要對SOAP消息進行四次處理。基本上都是"簽名'加密'解密'驗證"的過程。

          創(chuàng)建相關密匙庫
          客戶端和服務端都有相關的密匙庫,其中:

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

          你可以使用以下的批處理腳本創(chuàng)建上面四個密匙庫。

          例程6 創(chuàng)建相關密匙庫(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導出數(shù)字證書。
          keytool -export -file test_axis.cer %KS_PASS% -keystore server.keystore
          #從服務器的數(shù)字證書導出到客戶端信任的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導出數(shù)字證書。
          keytool -export -file test_axis.cer %KS_PASS% -keystore client.keystore
          #從客戶端的數(shù)字證書導出到服務器信任的truststore中。
          keytool -import -file test_axis.cer %KS_PASS% -keystore server.truststore -alias clientkey -noprompt
          
          #end
          

          簽名、加密、解密、身份驗證的實現(xiàn)
          對SOAP消息的簽名、加密、解密、身份驗證都放在一個名為WSSHelper的類中進行。

          例程7 簽名、加密、解密、身份驗證功能的實現(xiàn)――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文檔進行數(shù)字簽名。
               */
          	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實現(xiàn)了RSA加密、解密算法。當然,你也可以使用其它的安全提供者,并且可以使用不同的加密算法。可以從網(wǎng)絡上下載ISNetworks相關包。

          WSSHelper中包含了一個WsUtils類,它的功能就是從加密后的SOAP消息中刪除一些WS-Security元素,刪除這些元素后的SOAP消息才能被最終的客戶端或者Web服務端處理。

          服務器端Handler開發(fā)
          當請求到達后,服務端Handler調用handleRequest方法,執(zhí)行如下過程:對請求SOAP消息解密'身份驗證'刪除WSS元素'把Document轉換成SOAP消息。 Web服務端點對請求做出響應后,將調用handleResponse方法,執(zhí)行如下過程:對響應的SOAP消息進行數(shù)字簽名'加密'把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("在處理請求時發(fā)生了異常: " + e);
          				e.printStackTrace();
          				return false;
          			}
          		} else {
          			System.out.println("MessageContext是以下類的實例: " + messageContext.getClass());
          		}
          		 System.out.println("處理請求完畢!");
                  return true;
              }
              
              /**
               *處理響應
               *流程:數(shù)字簽名-->加密-->把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("在處理響應時發(fā)生以下錯誤: " + e);
          				e.printStackTrace();
          				return false;
          			}
          		}
          	
          		System.out.println("處理響應完畢!");
                  return true;
              }
              /**
               *初始化,主要是初始化一些相關參數(shù)。
               */
               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");
          		…//其它參數(shù)初始化	
              }
          	…
          }
          

          客戶端Handler開發(fā)
          客戶端Handler可以是任何JAX-RPC兼容的Handler處理器。比如AXIS Handler實現(xiàn)或者SUN 提供的JAX-RPC Handler參考實現(xiàn)。這里使用后者來作為客戶端Handler處理器。

          客戶端Handler和服務器端Handler原理一樣,但處理過程完全相反。

          例程9 客戶端Handler(WSSecurityClientHandler.java)
          
          package com.hellking.study.webservice;
          …
          
          //客戶端Handler
          public class WSSecurityClientHandler implements Handler 
          {       
                 //密匙庫相關信息   
          	    ...
          		
          	    /**
          	     *處理請求
          	     *流程:數(shù)字簽名-->加密-->把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;
              }
              /**
               *初始化,主要是初始化一些相關參數(shù)。
               */
               public void init(HandlerInfo config) {
          		…
              }
          	…
          }
          

          部署服務器端Handler
          為了使用Handler,需要在Web服務部署描述符中指定使用此Handler。Handler包含的初始化參數(shù)也在此描述,如例程10所示。

          例程10 服務器端Handler部署代碼
          
            <service name="PersonalTaxServicePort" provider="java:RPC">
            <parameter name="allowedMethods" value="*"/>
            <parameter name="className" value="/blog/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="/blog/com.hellking.study.webservice.WSSecurityServerHandler"/>
              <parameter name="keyStoreFile"
                      value="/blog/K:\\jakarta-tomcat-5.0.16\\server.keystore"/>
              <parameter name="trustStoreFile"
                      value="/blog/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="/blog/com.hellking.study.webservice.WSSecurityServerHandler"/>
              <parameter name="keyStoreFile"
                      value="/blog/K:\\jakarta-tomcat-5.0.16\\server.keystore"/>
          
              <parameter name="trustStoreFile"
                      value="/blog/K:\\jakarta-tomcat-5.0.16\\server.truststore"/>
              <parameter name="certAlias" value="clientkey"/>
             </handler>
            </responseFlow>
            </service>
            

          requestFlow表示W(wǎng)eb服務PersonalTaxServicePort的請求處理Handler鏈。這里只有一個Handler,就是WSSecurityServerHandler。當Web服務請求到達PersonalTaxServicePort時,WSSecurityServerHandler的handleRequest方法將被自動調用。

          注意:部署時,請改變Handler相關參數(shù)以和目標的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的相關參數(shù)。當然,JAX-RPC"參考實現(xiàn)"還支持在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,需要根據(jù)情況更改。            
                      String endpointURL = "http://localhost:8080/axis/services/PersonalTaxServicePort";
                      String wsdlURL=endpointURL+"?wsdl";
                      java.net.URL targetURL= new java.net.url(/blog/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協(xié)議來作為Web服務的傳輸協(xié)議!");			
                      System.out.println("已經(jīng)成功調用。請參看服務端的輸出!");
                      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參考實現(xiàn)",所以必須把jaxrpc-impl.jar包設置在CLASSPATH環(huán)境變量中,并且不要把axis.jar設置在客戶端CLASSPATH環(huán)境變量,否則會出現(xiàn)ClassCastException異常。這是因為axis也是JAX-RPC的實現(xiàn),如果它在CLASSPATH環(huán)境變量中,當調用:

          ServiceFactory svcFactory = ServiceFactory.newInstance()方法時,就可能初始化一個axis的ServiceFactory 實現(xiàn)。

          本文源代碼中client目錄下wss-client.bat文件包含了執(zhí)行WSSClient腳本,修改了部分環(huán)境變量參數(shù)后,才能執(zhí)行。

          總結
          本文和上一篇文章介紹了幾種不同實現(xiàn)Web服務安全的方法,你可以根據(jù)具體應用對安全的要求級別采用不同的方式。對于安全級別要求不高的應用,可以采用在Web服務器上使用基本認證、使用Axis的Handler或者使用Servlet過濾器來實現(xiàn)訪問控制;對于安全要求高的應用,可以采用本篇介紹開發(fā)的"axis下實現(xiàn)WS-Security的通用應用框架"來實現(xiàn)安全性。

          posted on 2006-08-04 20:08 SIMONE 閱讀(1129) 評論(0)  編輯  收藏 所屬分類: AXIS
          主站蜘蛛池模板: 东阿县| 时尚| 平利县| 蒙自县| 新丰县| 土默特左旗| 龙胜| 临邑县| 洛川县| 宜良县| 新泰市| 宁明县| 浦北县| 贵州省| 五指山市| 盐亭县| 石河子市| 屏山县| 凭祥市| 汉寿县| 新乡县| 张家口市| 宣化县| 长寿区| 桐乡市| 绥中县| 南华县| 洮南市| 京山县| 镇沅| 靖江市| 札达县| 馆陶县| 松滋市| 琼结县| 天津市| 阜新市| 海城市| 五河县| 讷河市| 南康市|