Web 服務(wù)安全性相關(guān)技術(shù)和開發(fā)工具
Web 服務(wù)安全性規(guī)范是一套可以幫助 Web 服務(wù)開發(fā)者保證 SOAP 消息交換的安全的機(jī)制。WS-Security 特別描述了對現(xiàn)有的 SOAP 消息傳遞的增強(qiáng),從而通過對 SOAP 消息應(yīng)用消息完整性、消息機(jī)密性和單消息認(rèn)證提供了保護(hù)級別。這些基本機(jī)制可以通過各種方式聯(lián)合,以適應(yīng)構(gòu)建使用多種加密技術(shù)的多種安全性模型。
圍繞Web服務(wù)的安全,有很多相關(guān)的技術(shù),比如WS-Security,WS-Trace等,另外,還有以下相關(guān)技術(shù):
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做詳細(xì)的探討,你可以在develperWorks Web 服務(wù)安全專題找到許多相關(guān)資料(見參考資料)。
Trust Services Integration Kit提供了一個WS-Security實現(xiàn)。你可以從http://www.xmltrustcenter.org獲得相關(guān)庫文件,分別是wssecurity.jar和tsik.jar。wssecurity.jar中包含一個WSSecurity類,可以使用它來對XML進(jìn)行數(shù)字簽名和驗證,加密與解密。
下面我們使用WS-Security來對SOAP消息進(jìn)行數(shù)字簽名,然后再進(jìn)行驗證。
回頁首
SOAP消息的簽名和驗證
使用WSSecurity對SOAP消息數(shù)字簽名
在對SOAP消息進(jìn)行簽名前,首先生成一個keystore。keystore包含了進(jìn)行數(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
#生成服務(wù)器端keystore。
keytool -genkey -dname %SERVER_DN% %KS_PASS% %KS_TYPE% -keystore
server.keystore %KEYINFO% -keypass changeit
SignAndVerifySoap類中包含了一個對XML進(jìn)行簽名的方法,它就是sign(),這個方法將對SOAP消息進(jìn)行簽名,然后輸出和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進(jìn)行簽名
*/
public void sign()
{
try
{
System.out.println("開始對SOAP消息進(jìn)行簽名,使用的密匙庫:" + KEY_STORE + "\n");
// 獲得私有key和相關(guān)證書,請參考JAVA安全編程相關(guān)書籍
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消息進(jìn)行簽名
wsSecurity.sign(source, signingKey, keyInfo);
// 保存簽名后的SOAP消息
writeFile(source, new FileOutputStream(TARGET_FILE));
System.out.println("把簽名后的文件寫入: " + TARGET_FILE + ",請查看結(jié)果!");
}
catch(Exception e)
{
e.printStackTrace();
}
}
在執(zhí)行此程序前,請把wssecurity.jar、source.xml和tsik.jar設(shè)置到類路徑環(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> 描述了已簽署的消息內(nèi)容。<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 指出了簽名算法(Signature Method Algorithm)。這個算法被用來將規(guī)范算法的輸出轉(zhuǎn)換成簽名值(Signature Value)。Key Info 元素包含的部分就是數(shù)字證書本身。
對簽名的SOAP消息進(jìn)行驗證
對SOAP消息進(jìn)行驗證就是使用keystore的信息生成TrustVerifier對象,然后調(diào)用WSSecurity的verify方法進(jìn)行驗證。
例程5 驗證簽名后的SOAP消息
/**
*驗證已經(jīng)簽名的SOAP消息
*/
public void verify()
{
try
{
System.out.println("開始檢驗SOAP消息,使用的密匙庫:" + KEY_STORE + "\n");
// 獲得私有key和相關(guān)證書,請參考JAVA安全編程相關(guān)書籍
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("檢驗結(jié)果:");
for (int len = 0; len < resa.length; len++){
System.out.println("result[" + len + "] = " + (resa[len].isValid()?"驗證通過":"驗證不通過"));
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
執(zhí)行SignAndVerifySoap的verify方法,可以看到類似以下的結(jié)果。
圖1 對SOAP消息進(jìn)行驗證
回頁首
在AXIS下實現(xiàn)WS-Security的應(yīng)用框架
待開發(fā)的應(yīng)用開發(fā)框架基于Handler實現(xiàn),將達(dá)到以下目標(biāo):此框架基于JAX-RPC環(huán)境下實現(xiàn)WS-Security應(yīng)用,它可以部署到任何需要實現(xiàn)WS-Security的axis環(huán)境下的Web服務(wù)應(yīng)用中,同時具體的應(yīng)用程序不做任何編碼修改。
由于此基于Handler實現(xiàn),我們有必要回顧一下Handler的一些基礎(chǔ)知識。
SOAP消息Handler能夠訪問代表RPC請求或者響應(yīng)的SOAP消息。在JAX-RPC技術(shù)中,SOAP消息Handler可以部署在服務(wù)端,也可以在客戶端使用。
SOAP消息Handler非常像Servlet技術(shù)中的Filter,它們共同的特點是請求發(fā)送到目標(biāo)前,Handler/Filter可以截取這些請求,并對請求做一些處理,從而達(dá)到一些輔助的功能。多個Handler可以組成一個Handler鏈,鏈上的每個Handler都完成某個特定的任務(wù)。比如有的Handler進(jìn)行權(quán)限驗證,有的Handler進(jìn)行日志處理等。關(guān)于Handler更詳細(xì)的介紹,請參考本系列文章《 J2EE Web服務(wù)開發(fā)系列之六: 使用Handler來增強(qiáng)Web服務(wù)的功能》。
實現(xiàn)原理
圖2是此例子具體實現(xiàn)原理圖。
圖2 Handler結(jié)合WSSecurity實現(xiàn)Web服務(wù)安全的工作原理
處理流程如下:
1、 客戶端(WSSClient)發(fā)出調(diào)用Web服務(wù)請求;
2、 客戶端Handler(WSSecurityClientHandler)截獲請求的SOAP消息;
3、 客戶端Handler對截獲的SOAP消息進(jìn)行數(shù)字簽名(使用client.keystore作為簽名依據(jù));
4、 客戶端Handler對簽名后的SOAP消息進(jìn)行加密(使用RSA算法加密);
5、 被加密的SOAP消息通過互聯(lián)網(wǎng)傳送到目標(biāo)Web服務(wù)端口;
6、 服務(wù)器端Handler(WSSecurityServerHandler)截獲加密的SOAP消息;
7、 服務(wù)器端Handler對加密的SOAP消息進(jìn)行解密;
8、 服務(wù)器端Handler對SOAP消息進(jìn)行身份驗證(server.truststore包含了所信任的身份信息),如果驗證不通過,將拋出異常;
9、 服務(wù)器端Handler刪除被解密后的SOAP消息中與WS-Security相關(guān)的元素;
10、 解密后的原始SOAP消息被發(fā)送到目標(biāo)Web服務(wù)端口(如TaxService);
11、 目標(biāo)Web服務(wù)對Web服務(wù)請求進(jìn)行處理,然后返回響應(yīng)的SOAP消息;
12、 服務(wù)器端Handler截獲響應(yīng)的SOAP消息;
13、 服務(wù)器端Handler對截獲的SOAP消息進(jìn)行數(shù)字簽名(使用server.keystore作為簽名依據(jù));
14、 服務(wù)器端Handler對簽名后的SOAP消息進(jìn)行加密(使用RSA算法加密);
15、 被加密的SOAP消息通過互聯(lián)網(wǎng)傳送到目客戶端;
16、 客戶端Handler截獲加密的SOAP消息;
17、 客戶端Handler對加密的SOAP消息進(jìn)行解密;
18、 客戶端Handler對SOAP消息進(jìn)行身份驗證(client.truststore包含了所信任的身份信息),如果驗證不通過,將拋出異常;
19、 客戶端Handler刪除被解密后的SOAP消息中與WS-Security相關(guān)的元素;
20、 被解密后的SOAP消息發(fā)送到目標(biāo)客戶端,客戶端輸出調(diào)用結(jié)果。
從上面可以看出,在一個SOAP調(diào)用回合中,要對SOAP消息進(jìn)行四次處理?;旧隙际?簽名'加密'解密'驗證"的過程。
創(chuàng)建相關(guān)密匙庫
客戶端和服務(wù)端都有相關(guān)的密匙庫,其中:
client.keystore:客戶端自身的身份信息;
client.truststore:客戶端所信任的身份信息,在此例中也就是包含了服務(wù)器的身份信息;
server.keystore:服務(wù)器自身的身份信息;
server.truststore:服務(wù)器所信任的身份信息(即客戶端身份信息)。
你可以使用以下的批處理腳本創(chuàng)建上面四個密匙庫。
例程6 創(chuàng)建相關(guān)密匙庫(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導(dǎo)出數(shù)字證書。
keytool -export -file test_axis.cer %KS_PASS% -keystore server.keystore
#從服務(wù)器的數(shù)字證書導(dǎo)出到客戶端信任的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導(dǎo)出數(shù)字證書。
keytool -export -file test_axis.cer %KS_PASS% -keystore client.keystore
#從客戶端的數(shù)字證書導(dǎo)出到服務(wù)器信任的truststore中。
keytool -import -file test_axis.cer %KS_PASS% -keystore server.truststore -alias clientkey -noprompt
#end
簽名、加密、解密、身份驗證的實現(xiàn)
對SOAP消息的簽名、加密、解密、身份驗證都放在一個名為WSSHelper的類中進(jìn)行。
例程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文檔進(jìn)行數(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文檔進(jìn)行身份驗證。
*/
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文檔進(jìn)行加密。必須有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();
}
}
/**
*對文檔進(jìn)行解密。
*/
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相關(guān)的元素。
}
}
WSSHelper類中使用了ISNetworks安全提供者,ISNetworks實現(xiàn)了RSA加密、解密算法。當(dāng)然,你也可以使用其它的安全提供者,并且可以使用不同的加密算法。可以從網(wǎng)絡(luò)上下載ISNetworks相關(guān)包。
WSSHelper中包含了一個WsUtils類,它的功能就是從加密后的SOAP消息中刪除一些WS-Security元素,刪除這些元素后的SOAP消息才能被最終的客戶端或者Web服務(wù)端處理。
服務(wù)器端Handler開發(fā)
當(dāng)請求到達(dá)后,服務(wù)端Handler調(diào)用handleRequest方法,執(zhí)行如下過程:對請求SOAP消息解密'身份驗證'刪除WSS元素'把Document轉(zhuǎn)換成SOAP消息。 Web服務(wù)端點對請求做出響應(yīng)后,將調(diào)用handleResponse方法,執(zhí)行如下過程:對響應(yīng)的SOAP消息進(jìn)行數(shù)字簽名'加密'把Document轉(zhuǎn)換成SOAP消息。
例程8 服務(wù)器端Handler(WSSecurityServerHandler.java)
package com.hellking.study.webservice;
...
//服務(wù)器端Handler
public class WSSecurityServerHandler implements Handler
{
//密匙庫相關(guān)信息
private String keyStoreFile = null;
private String keyStoreType = "JKS";
。。。
public WSSecurityServerHandler()
{
System.out.println("服務(wù)端Handler:構(gòu)造方法");
}
/**
*處理請求
*流程:解密-->身份驗證-->刪除WSS元素'把Document轉(zhuǎn)換成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;
}
/**
*處理響應(yīng)
*流程:數(shù)字簽名-->加密-->把Document轉(zhuǎn)換成SOAP消息。
*/
public boolean handleResponse(MessageContext messageContext) {
System.out.println("開始處理Web服務(wù)響應(yīng)。。。");
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("在處理響應(yīng)時發(fā)生以下錯誤: " + e);
e.printStackTrace();
return false;
}
}
System.out.println("處理響應(yīng)完畢!");
return true;
}
/**
*初始化,主要是初始化一些相關(guān)參數(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和服務(wù)器端Handler原理一樣,但處理過程完全相反。
例程9 客戶端Handler(WSSecurityClientHandler.java)
package com.hellking.study.webservice;
…
//客戶端Handler
public class WSSecurityClientHandler implements Handler
{
//密匙庫相關(guān)信息
...
/**
*處理請求
*流程:數(shù)字簽名-->加密-->把Document轉(zhuǎn)換成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;
}
/**
*處理響應(yīng)
*流程:解密-->身份驗證-->刪除WSS元素'把Document轉(zhuǎn)換成SOAP消息。
*/
public boolean handleResponse(MessageContext messageContext) {
System.out.println("開始處理Web服務(wù)響應(yīng)。。。");
…
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("處理響應(yīng)完畢!");
return true;
}
/**
*初始化,主要是初始化一些相關(guān)參數(shù)。
*/
public void init(HandlerInfo config) {
…
}
…
}
部署服務(wù)器端Handler
為了使用Handler,需要在Web服務(wù)部署描述符中指定使用此Handler。Handler包含的初始化參數(shù)也在此描述,如例程10所示。
例程10 服務(wù)器端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表示W(wǎng)eb服務(wù)PersonalTaxServicePort的請求處理Handler鏈。這里只有一個Handler,就是WSSecurityServerHandler。當(dāng)Web服務(wù)請求到達(dá)PersonalTaxServicePort時,WSSecurityServerHandler的handleRequest方法將被自動調(diào)用。
注意:部署時,請改變Handler相關(guān)參數(shù)以和目標(biāo)的Web服務(wù)一致,比如trustStoreFile的路徑等。
調(diào)用測試
這里采用代理的方式來調(diào)用Web服務(wù),先編寫一個Web服務(wù)接口。
例程11 TaxServiceInterface
package com.hellking.study.webservice;
…
/**
*個人所得稅Web服務(wù)。
*/
public interface TaxServiceInterface extends Remote
{
public double getTax(double salary)throws java.rmi.RemoteException;
}
WSSClient客戶端程序是通過代理的方式來訪問Web服務(wù)的。由于要使用Handler,所以在訪問前通過registerHandlers()方法注冊了WSSecurityClientHandler,并且初始化了WSSecurityClientHandler的相關(guān)參數(shù)。當(dāng)然,JAX-RPC"參考實現(xiàn)"還支持在Web服務(wù)客戶端配置文件中描述Handler信息,這樣就不需要在客戶端代碼中對Handler進(jìn)行注冊了,你可以參考相關(guān)文檔。
例程12 測試客戶端程序(WSSClient)
package com.hellking.study.webservice;
...
/**
*調(diào)用需要驗證的Web服務(wù)
*/
public class WSSClient
{
static final double salary=5000;
public static void main(String [] args)
{
try {
//服務(wù)端的url,需要根據(jù)情況更改。
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協(xié)議來作為Web服務(wù)的傳輸協(xié)議!");
System.out.println("已經(jīng)成功調(diào)用。請參看服務(wù)端的輸出!");
System.out.println("輸入工資"+salary+"元,應(yīng)交個人所得稅:"+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包設(shè)置在CLASSPATH環(huán)境變量中,并且不要把a(bǔ)xis.jar設(shè)置在客戶端CLASSPATH環(huán)境變量,否則會出現(xiàn)ClassCastException異常。這是因為axis也是JAX-RPC的實現(xiàn),如果它在CLASSPATH環(huán)境變量中,當(dāng)調(diào)用:
ServiceFactory svcFactory = ServiceFactory.newInstance()方法時,就可能初始化一個axis的ServiceFactory 實現(xiàn)。
本文源代碼中client目錄下wss-client.bat文件包含了執(zhí)行WSSClient腳本,修改了部分環(huán)境變量參數(shù)后,才能執(zhí)行。