|
Posted on 2007-04-07 15:57 默認為零 閱讀(637) 評論(1) 編輯 收藏 所屬分類: 技術
前段時間摸索過基于信息安全的Web service開發,今天休息閑著也憋得慌,正好整理一下(參考網絡資料)。這里使用了WS-Security規范對SOAP包進行加密與身份認證。
首先從http://www.xmltrustcenter.org獲得相關庫文件,分別是ws-security.jar和tsik.jar。ws-security.jar中包含一個WSSecurity類,我們使用它來對XML進行數字簽名和驗證,加密與解密。
這里使用ISNetworks安全提供者,ISNetworks實現了RSA加密、解密算法。參考相關包ISNetworksProvider.jar。
使用JDK自帶的工具創建密匙庫和信任庫,因為在請求和響應過程中都需要使用,所以需要生成兩對。建立批處理文件gen-cer-store.bat內容如下:
set SERVER_DN="CN=Server, OU=ec, O=ec, L=BEIJINGC, S=BEIJING, C=CN"
set CLIENT_DN="CN=Client, OU=ec, O=ec, L=BEIJING, S=BEIJING, C=CN"
set KS_PASS=-storepass changeit
set KEYINFO=-keyalg RSA
keytool -genkey -alias Server -dname %SERVER_DN% %KS_PASS% -keystore server.keystore %KEYINFO% -keypass changeit
keytool -export -alias Server -file test_axis.cer %KS_PASS% -keystore server.keystore
keytool -import -file test_axis.cer %KS_PASS% -keystore client.truststore -alias serverkey -noprompt
keytool -genkey -alias Client -dname %CLIENT_DN% %KS_PASS% -keystore client.keystore %KEYINFO% -keypass changeit
keytool -export -alias Client -file test_axis.cer %KS_PASS% -keystore client.keystore
keytool -import -file test_axis.cer %KS_PASS% -keystore server.truststore -alias clientkey -noprompt
這里具體內容含義不解釋了,自己可以在網絡上找來看看。
執行gen-cer-store.bat之后,我們可以得到四個文件server.keystore,server.truststore,client.keystore,client.truststore。
基本流程如下:
1、 客戶端(WSSClient)發出調用Web服務請求;
2、 客戶端Handler(WSClientRequestHandler)截獲請求的SOAP消息;
3、 客戶端Handler對截獲的SOAP消息進行數字簽名(使用client.keystore作為簽名依據);
4、 客戶端Handler對簽名后的SOAP消息進行加密(使用RSA算法加密);
5、 被加密的SOAP消息通過互聯網傳送到目標Web服務端口;
6、 服務器端Handler(WSServerRequestHandler)截獲加密的SOAP消息;
7、 服務器端Handler對加密的SOAP消息進行解密;
8、 服務器端Handler對SOAP消息進行身份驗證(server.truststore包含了所信任的身份信息),如果驗證不通過,將拋出異常;
9、 服務器端Handler刪除被解密后的SOAP消息中與WS-Security相關的元素;
10、 解密后的原始SOAP消息被發送到目標Web服務端口;
11、 目標Web服務對Web服務請求進行處理,然后返回響應的SOAP消息;
12、 服務器端Handler(WSServerResponseHandler)截獲響應的SOAP消息;
13、 服務器端Handler對截獲的SOAP消息進行數字簽名(使用server.keystore作為簽名依據);
14、 服務器端Handler對簽名后的SOAP消息進行加密(使用RSA算法加密);
15、 被加密的SOAP消息通過互聯網傳送到目客戶端;
16、 客戶端Handler(WSClientResponseHandler)截獲加密的SOAP消息;
17、 客戶端Handler對加密的SOAP消息進行解密;
18、 客戶端Handler對SOAP消息進行身份驗證(client.truststore包含了所信任的身份信息),如果驗證不通過,將拋出異常;
19、 客戶端Handler刪除被解密后的SOAP消息中與WS-Security相關的元素;
20、 被解密后的SOAP消息發送到目標客戶端,客戶端輸出調用結果。
從上面流程可以看出,在一個SOAP調用回合中,需要對SOAP消息進行四次處理,在請求和響應中都是“簽名” -> “加密” -> “解密” -> “驗證”的過程。
程序框架如下:
WSClientHandler.java //基類,包含了一些公用方法
WSClientRequestHandler.java //繼承于WSClientHandler.java,調用WSHelper.java對客戶端發出的XML文檔進行加密
WSClientResponseHandler.java //繼承于WSClientHandler.java,調用WSHelper.java對服務器端返回的XML文檔進行解密
WSServerHandler.java //基類,包含了一些公用方法
WSServerRequestHandler.java //繼承于WSServerHandler.java,調用WSHelper.java對客戶端發出的加密后的XML文檔進行解密
WSServerResponseHandler.java//繼承于WSServerHandler.java,調用WSHelper.java對服務器端返回的XML文檔進行加密
WSSecurityHelper.java //核心類,對SOAP消息簽名、加密、解密、身份驗證
MessageConverter.java //幫助類,Document、SOAP消息互相轉換
WSSecurityHelper.java主要包括方法如下:
 /** *//**
* 數字簽名
*/
public static void sign(Document doc, String keystore, String storetype,
 String storepass, String alias, String keypass) throws Exception {
InputStream is = WSSecurityHelper.class.getResourceAsStream(keystore);
KeyStore keyStore = KeyStore.getInstance(storetype);
keyStore.load(is, 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 security = new WSSecurity();
security.sign(doc, sk, ki);
}

 /** *//**
* 身份驗證
*/
public static boolean verify(Document doc, String keystore,
 String storetype, String storepass) throws Exception {
InputStream is = WSSecurityHelper.class.getResourceAsStream(keystore);
KeyStore keyStore = KeyStore.getInstance(storetype);
keyStore.load(is, storepass.toCharArray());

TrustVerifier verifier = new X509TrustVerifier(keyStore);

WSSecurity security = new WSSecurity();
MessageValidity[] resa = security.verify(doc, verifier, null, null);
 if (resa.length > 0) {
return resa[0].isValid();
}
return false;
}

 /** *//**
* 加密
*/
public static void encrypt(Document doc, String keystore, String storetype,
 String storepass, String alias) throws Exception {
 try {
InputStream is = WSSecurityHelper.class
.getResourceAsStream(keystore);
KeyStore keyStore = KeyStore.getInstance(storetype);
keyStore.load(is, storepass.toCharArray());
X509Certificate cert = (X509Certificate) keyStore
.getCertificate(alias);// serverkey
KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede",
PROVIDER);

keyGenerator.init(168, new SecureRandom());
SecretKey key = keyGenerator.generateKey();

KeyInfo ki = new KeyInfo();
ki.setCertificate(cert);

PublicKey pubk = cert.getPublicKey();
WSSecurity security = new WSSecurity();
security.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 {
InputStream is = WSSecurityHelper.class.getResourceAsStream(keystore);
java.security.KeyStore keyStore = java.security.KeyStore
.getInstance(storetype);
keyStore.load(is, storepass.toCharArray());
PrivateKey prvk2 = (PrivateKey) keyStore.getKey(alias, keypass
.toCharArray());

WSSecurity security = new WSSecurity();
security.decrypt(doc, prvk2, null);
removeWSSElements(doc);
}
MessageConverter.java實現如下:
package com.macrowing.sys.security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

 /** *//**
* Document對象與SOAPMessage對象相互轉換
*/
 public class MessageConverter {

 /** *//**
* Document轉換成SOAPMessage
*/
public static SOAPMessage convertDocumentToSOAPMessage(Document doc)
throws TransformerConfigurationException, TransformerException,
 SOAPException, IOException {
TransformerFactory transformerFactory = TransformerFactory
.newInstance();
Transformer transformer = transformerFactory.newTransformer();

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
transformer.transform(new DOMSource(doc), new StreamResult(
byteArrayOutputStream));
MimeHeaders header = new MimeHeaders();
header.addHeader("Content-Type", "text/xml");
MessageFactory factory = MessageFactory.newInstance();
SOAPMessage soapMsg = factory.createMessage(header,
new ByteArrayInputStream(byteArrayOutputStream.toByteArray(),
0, byteArrayOutputStream.size()));
return soapMsg;
}

 /** *//**
* SOAPMessage轉換成Document
*/
public static Document convertSoapMessageToDocument(SOAPMessage soapMsg)
throws ParserConfigurationException, SAXException, SOAPException,
 IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
soapMsg.writeTo(byteArrayOutputStream);
ByteArrayInputStream bais = new ByteArrayInputStream(
byteArrayOutputStream.toByteArray(), 0, byteArrayOutputStream
.size());

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory
.newDocumentBuilder();
Document doc = documentBuilder.parse(bais);
return doc;
}
}
WSClientHandler.java作為一個抽象類,主要初始化一些相關信息,invoke方法由其子類實現,代碼如下:
package com.macrowing.sys.security;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.handlers.BasicHandler;

 public abstract class WSClientHandler extends BasicHandler {

private static final long serialVersionUID = -4494231325476939491L;

protected String keyStoreFile;
protected String keyStoreType = "JKS";// 默認
protected String keyStorePassword;
protected String keyAlias;
protected String keyEntryPassword;
protected String trustStoreFile;
protected String trustStoreType = "JKS";// 默認
protected String trustStorePassword;
protected String certAlias;

public void setInitialization(String keyStoreFile, String keyStoreType,
String keyStorePassword, String keyAlias, String keyEntryPassword,
String trustStoreFile, String trustStoreType,
 String trustStorePassword, String certAlias) {
this.keyStoreFile = keyStoreFile;
this.keyStoreType = keyStoreType;
this.keyStorePassword = keyStorePassword;
this.keyAlias = keyAlias;
this.keyEntryPassword = keyEntryPassword;
this.trustStoreFile = trustStoreFile;
this.trustStoreType = trustStoreType;
this.trustStorePassword = trustStorePassword;
this.certAlias = certAlias;
}

public void setInitialization(String keyStoreFile, String keyStorePassword,
String keyAlias, String keyEntryPassword, String trustStoreFile,
 String trustStorePassword, String certAlias) {
this.keyStoreFile = keyStoreFile;
this.keyStorePassword = keyStorePassword;
this.keyAlias = keyAlias;
this.keyEntryPassword = keyEntryPassword;
this.trustStoreFile = trustStoreFile;
this.trustStorePassword = trustStorePassword;
this.certAlias = certAlias;
}

public abstract void invoke(MessageContext messageContext) throws AxisFault;

 public void onFault(MessageContext msgContext) {
}

}
WSClientRequestHandler.java實現代碼如下:
package com.macrowing.sys.security;

import javax.xml.soap.SOAPMessage;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.w3c.dom.Document;

 public class WSClientRequestHandler extends WSClientHandler {

private static final long serialVersionUID = -2966519798377660568L;

 public void invoke(MessageContext messageContext) throws AxisFault {
System.out.println("1.客戶端發出請求");
 try {
SOAPMessage soapMessage = messageContext.getMessage();
Document doc = MessageConverter
.convertSoapMessageToDocument(soapMessage);

WSSecurityHelper.sign(doc, keyStoreFile, keyStoreType,
keyStorePassword, keyAlias, keyEntryPassword);

WSSecurityHelper.encrypt(doc, trustStoreFile, trustStoreType,
trustStorePassword, certAlias);

soapMessage = MessageConverter.convertDocumentToSOAPMessage(doc);
messageContext.setMessage(soapMessage);
 } catch (Exception e) {
System.err.println("在處理響應時發生以下錯誤: " + e);
e.printStackTrace();
}
}

}
WSClientResponseHandler.java實現代碼如下:
package com.macrowing.sys.security;

import javax.xml.soap.SOAPMessage;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.w3c.dom.Document;

 public class WSClientResponseHandler extends WSClientHandler {

private static final long serialVersionUID = -8103853123763763505L;

 public void invoke(MessageContext messageContext) throws AxisFault {
System.out.println("4.客戶端接收響應");
 try {
SOAPMessage soapMessage = messageContext.getCurrentMessage();
Document doc = MessageConverter
.convertSoapMessageToDocument(soapMessage);

WSSecurityHelper.decrypt(doc, keyStoreFile, keyStoreType,
keyStorePassword, keyAlias, keyEntryPassword);

WSSecurityHelper.verify(doc, trustStoreFile, trustStoreType,
trustStorePassword);

WSSecurityHelper.removeWSSElements(doc);
soapMessage = MessageConverter.convertDocumentToSOAPMessage(doc);
messageContext.setMessage(soapMessage);
 } catch (Exception e) {
e.printStackTrace();
System.err.println("在處理響應時發生以下錯誤: " + e);
}
}

}
WSServerHandler.java同樣作為一個抽象類,主要初始化一些相關信息,invoke方法由其子類實現,代碼如下:
package com.macrowing.sys.security;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.handlers.BasicHandler;

 public abstract class WSServerHandler extends BasicHandler {

private static final long serialVersionUID = -5333475846617822121L;

protected String keyStoreFile;
protected String keyStoreType = "JKS";// 默認
protected String keyStorePassword;
protected String keyAlias;
protected String keyEntryPassword;
protected String trustStoreFile;
protected String trustStoreType = "JKS";// 默認
protected String trustStorePassword;
protected String certAlias;

 public void onFault(MessageContext msgContext) {
}

 /** *//**
* 初始化,從配置文件server-config.wsdd中讀取屬性
*/
 public void init() {
keyStoreFile = (String) getOption("keyStoreFile");
 if ((keyStoreFile == null)) {
System.err
.println("Please keyStoreFile configured for the Handler!");
}

trustStoreFile = (String) getOption("trustStoreFile");
 if ((trustStoreFile == null)) {
System.err
.println("Please trustStoreFile configured for the Handler!");
}

keyStorePassword = (String) getOption("keyStorePassword");
 if ((keyStorePassword == null)) {
System.err
.println("Please keyStorePassword configured for the Handler!");
}

keyAlias = (String) getOption("keyAlias");
 if ((keyAlias == null)) {
System.err.println("Please keyAlias configured for the Handler!");
}

keyEntryPassword = (String) getOption("keyEntryPassword");
 if ((keyEntryPassword == null)) {
System.err
.println("Please keyEntryPassword configured for the Handler!");
}

trustStorePassword = (String) getOption("trustStorePassword");
 if ((trustStorePassword == null)) {
System.err
.println("Please trustStorePassword configured for the Handler!");
}

certAlias = (String) getOption("certAlias");
 if ((certAlias == null)) {
System.err.println("Please certAlias configured for the Handler!");
}

 if ((getOption("keyStoreType")) != null) {
keyStoreType = (String) getOption("keyStoreType");
}

 if ((getOption("trustStoreType")) != null) {
trustStoreType = (String) getOption("trustStoreType");
}
}

public abstract void invoke(MessageContext arg0) throws AxisFault;
}
WSServerRequestHandler.java實現代碼如下:
package com.macrowing.sys.security;

import javax.xml.soap.SOAPMessage;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.w3c.dom.Document;

 public class WSServerRequestHandler extends WSServerHandler {

private static final long serialVersionUID = 6462778870280567942L;

 public void invoke(MessageContext messageContext) throws AxisFault {
System.out.println("2.服務端接收請求");
 try {
SOAPMessage msg = messageContext.getCurrentMessage();
Document doc = MessageConverter.convertSoapMessageToDocument(msg);

WSSecurityHelper.decrypt(doc, keyStoreFile, keyStoreType,
keyStorePassword, keyAlias, keyEntryPassword);

WSSecurityHelper.verify(doc, trustStoreFile, trustStoreType,
trustStorePassword);

WSSecurityHelper.removeWSSElements(doc);
msg = MessageConverter.convertDocumentToSOAPMessage(doc);
messageContext.setMessage(msg);
 } catch (Exception e) {
e.printStackTrace();
System.err.println("在處理響應時發生以下錯誤: " + e);
}
}

}
WSServerResponseHandler.java實現代碼如下:
package com.macrowing.sys.security;

import javax.xml.soap.SOAPMessage;

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.w3c.dom.Document;

 public class WSServerResponseHandler extends WSServerHandler {

private static final long serialVersionUID = -2376918924804535243L;

 public void invoke(MessageContext messageContext) throws AxisFault {
System.out.println("3.服務端響應請求");
 try {
SOAPMessage soapMessage = messageContext.getMessage();
Document doc = MessageConverter
.convertSoapMessageToDocument(soapMessage);

WSSecurityHelper.sign(doc, keyStoreFile, keyStoreType,
keyStorePassword, keyAlias, keyEntryPassword);

WSSecurityHelper.encrypt(doc, trustStoreFile, trustStoreType,
trustStorePassword, certAlias);

soapMessage = MessageConverter.convertDocumentToSOAPMessage(doc);
messageContext.setMessage(soapMessage);
 } catch (Exception e) {
System.err.println("在處理響應時發生以下錯誤: " + e);
e.printStackTrace();
}
}

}
這樣,基于WS-Security規范的SOAP安全程序框架建立好了。為了使用方便,可以把所有的class打包成jar包放在%TOMCAT_HOME%\webapps\axis\WEB-INF\lib目錄下。然后,如上文所述,將服務端的Handler發布于server-config.wsdd文件中,如下:
<handler name="ServerRequestHandler" type="java:com.macrowing.sys.security.WSServerRequestHandler">
<parameter name="keyStoreFile" value="server.keystore"/>
<parameter name="trustStoreFile" value="server.truststore"/>
<parameter name="keyStorePassword" value="changeit"/>
<parameter name="keyAlias" value="Server"/>
<parameter name="keyEntryPassword" value="changeit"/>
<parameter name="trustStorePassword" value="changeit"/>
<parameter name="certAlias" value="clientkey"/>
</handler>
<handler name="ServerResponseHandler" type="java:com.macrowing.sys.security.WSServerResponseHandler">
<parameter name="keyStoreFile" value="server.keystore"/>
<parameter name="trustStoreFile" value="server.truststore"/>
<parameter name="keyStorePassword" value="changeit"/>
<parameter name="keyAlias" value="Server"/>
<parameter name="keyEntryPassword" value="changeit"/>
<parameter name="trustStorePassword" value="changeit"/>
<parameter name="certAlias" value="clientkey"/>
</handler>
并且不要忘記在需要保障信息安全的Web service中加入:
<requestFlow>
<handler type="soapmonitor"/>
<handler type="ServerRequestHandler"/>
</requestFlow>
<responseFlow>
<handler type="soapmonitor"/>
<handler type="ServerResponseHandler"/>
</responseFlow>
好了,服務端程序部署完了,編寫客戶端程序WSClient.java測試:
 public static void main(String[] args) {
 try {
String endpointURL = "http://localhost:8080/axis/services/Hello";

WSClientHandler handler = new WSClientRequestHandler();
handler.setInitialization("client.keystore", "changeit", "Client",
"changeit", "client.truststore", "changeit", "serverkey");
WSClientHandler handlee = new WSClientResponseHandler();
handlee.setInitialization("client.keystore", "changeit", "Client",
"changeit", "client.truststore", "changeit", "serverkey");

Service svc = new Service();
Call call = (Call) svc.createCall();
call.setClientHandlers(handler, handlee);
call.setTargetEndpointAddress(new URL(endpointURL));
call.setOperationName(new QName("sayHello"));

 call.invoke(new Object[] { "apos" });

 } catch (Exception e) {
e.printStackTrace();
}
}
啟動Tomcat,打開SOAP Monitor,然后運行客戶端,OK,你可以看到在monitor上看到需要的效果了。
評論
# re: 基于Axis 1.X的Web Service開發(三)[未登錄] 回復 更多評論
2012-11-07 10:13 by
受教了,轉了,謝謝博主
|