|
Posted on 2007-04-07 15:57 默认为零 阅读(636) 评论(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
受教了,转了,谢谢博主
|