 | 级别: 高级 Vikas Arora (vikaaror@in.ibm.com), 软件工程师, IBM, Software Group
2006 年 2 月 06 日 在本文中,您可以了解如何在 Internet 上安全地发送数据。在很多 B2B 应用程序中,社区合作伙伴通过简单对象访问协议(Simple Object Access Protocol,SOAP)和超文本传输协议(Hypertext Transfer Protocol,HTTP)使用标准的 Dun & Bradstreet (DUNS) ID、通信协议 Header 中的 Freeform ID 或文档本身中的 ID 来进行彼此间的通信。作者还给出了具体的 SOAP Header 示例来让您对其有个全面的了解。
引言
在实际的 B2B 场景中,社区合作伙伴通过若干协议来进行事务处理,例如简单对象访问协议/超文本传输协议 (SOAP/HTTP)、Application System 2/超文本传输协议 (AS2/HTTP)、Java™ Message Service (JMS)、文件传输协议 (FTP) 和简单邮件传输协议 (SMTP),您可以使用各种标准机制(如 RosettaNet 文档、cXML 文档、形式良好的 XML 文档)或采用自定义格式的其他文档(如 Excel),来标识嵌入到业务文档本身中的上述每种类型或协议的通信合作伙伴。
在这种特殊方法中,社区合作伙伴通过 SOAP 和 HTTP 执行业务事务,为了标识这些合作伙伴,您需要考虑采用以下机制:
- SOAP Header 中的合作伙伴 ID
- 业务文档中的 ID
SOAP Header 中的合作伙伴 ID:客户端
为了将合作伙伴 ID 放入 SOAP Header 中,您需要使用 Java APIs for XML-Based Remote Procedure Call (JAX-RPC) 实现中的处理程序。既可以对客户机使用处理程序,也可以将其用在服务器端上。在客户端,使用处理程序可能会比较麻烦,因为客户机应用程序是独立的 Java 客户机。在此方法中,您必须在客户机存根中配置 RPC 处理程序,以便对 MessageContext 对象进行修改,使得在该处理程序中包含合作伙伴的 ID。javax.xml.rpc.handler.MessageContext 对象是从客户机的调用 API 接收的,必须将其强制转换为 com.ibm.ws.webservices.engine.MessageContext,以便获取 MessageContext 对象的 IBM 实现,此实现可提供用于访问 SOAP 消息的其他应用程序编程接口 (API)。
如果使用 WSDL2Java.bat 文件生成相应的 Java 文件,则将获得以下客户机存根内容,如下面的清单 1 中所示:
清单 1. 来自 WSDL2Java.bat 文件的存根
public AttachmentBindingStub(java.net.URL endpointURL,
javax.xml.rpc.Service service)
throws com.ibm.ws.webservices.engine.WebServicesFault
{
if (service == null) {
super.service = new com.ibm.ws.webservices.engine.client.Service();
}
else {
super.service = service;
}
java.util.List handlerInfos = new java.util.Vector();
super.engine =
((com.ibm.ws.webservices.engine.client.Service)super.service).getEngine();
initTypeMapping();
super.cachedEndpoint = endpointURL;
super.connection =
((com.ibm.ws.webservices.engine.client.Service)super.service).
getConnection(endpointURL);
super.messageContexts =
new com.ibm.ws.webservices.engine.MessageContext[4];
}
|
要配置客户端的处理程序,需要修改以下代码,如下面的清单 2 中所示:
清单 2. 配置客户端的处理程序
public AttachmentBindingStub(java.net.URL endpointURL,
javax.xml.rpc.Service service)
throws com.ibm.ws.webservices.engine.WebServicesFault
{
if (service == null) {
super.service = new com.ibm.ws.webservices.engine.client.Service();
}
else {
super.service = service;
}
java.util.List handlerInfos = new java.util.Vector();
javax.xml.namespace.QName[] headers = null;
javax.xml.rpc.handler.HandlerInfo handlerInfo =
new javax.xml.rpc.handler.HandlerInfo(
tip.attachment.NameValidatorHandler.class,
null, headers);
handlerInfos.add(handlerInfo);
service.getHandlerRegistry().setHandlerChain(
new javax.xml.namespace.QName("AttachmentTip"),
handlerInfos);
super.engine =
((com.ibm.ws.webservices.engine.client.Service) super.service).
getEngine();
initTypeMapping();
super.cachedEndpoint = endpointURL;
super.connection =
((com.ibm.ws.webservices.engine.client.Service)super.service).
getConnection(endpointURL);
super.messageContexts =
new com.ibm.ws.webservices.engine.MessageContext[4];
}
|
在处理程序(此处的处理程序是 tip.attachment.NameValidatorHandler)的 handleRequest 方法中,您需要将 MessageContext 修改为包含发送合作伙伴的 ID 和接收合作伙伴的 ID,如下面的清单 3 中所示:
清单 3. handleRequest 方法
public boolean handleRequest(javax.xml.rpc.handler.MessageContext ctx)
{
try{
com.ibm.ws.webservices.engine.MessageContext soapMessageContext =
(com.ibm.ws.webservices.engine.MessageContext)ctx;
SOAPMessage sm = soapMessageContext.getMessage();
SOAPHeader soapHeader = sm.getSOAPPart().getEnvelope().getHeader();
javax.xml.soap.Name name = sm.getSOAPPart().getEnvelope().
createName("sender-id","b2b","http://ibm.com/b2b");
soapHeader.addHeaderElement(name).addTextNode("b2b");;
name = sm.getSOAPPart().getEnvelope().
createName("receiver-id","b2c","http://ibm.com/b2c");
soapHeader.addHeaderElement(name).addTextNode("b2c");
// variable for soap body
SOAPBody sb = null;
// get soap body from soap message
sb = sm.getSOAPPart().getEnvelope().getBody();
}catch(Exception ex){
ex.printStackTrace();
}
return true;
}
|
服务器端代码处理
当将合作伙伴 ID 放入到服务器端的 SOAP Header 中时,需要通过一个标准方法来在 webservices.xml 文件中配置该处理程序。进行此工作时,webservices.xml 文件将与下面的清单 4 类似:
清单 4. 将 ID 放入到 SOAP Header 中
<port-component>
<port-component-name>AttachmentTip
</port-component-name>
<wsdl-port xmlns:pfx=
"urn:attachment.tip">pfx:AttachmentTip</wsdl-port>
<service-endpoint-interface>tip.attachment.AttachmentTip
</service-endpoint-interface>
<service-impl-bean>
<servlet-link>AttachmentTip</servlet-link>
</service-impl-bean>
<handler>
<handler-name>NameValidatorHandler</handler-name>
<handler-class>tip.attachment.NameValidatorHandler
</handler-class>
</handler>
</port-component>
|
然后,您需要在 RPC 处理程序的服务器端访问此 SOAP 消息,以提取 SOAP Header 来标识合作伙伴的 ID。在服务器端处理程序中,必须将这些合作伙伴 ID 存储在外部存储中,并通过从此处读取合作伙伴 ID 来使用附件。
业务文档中的合作伙伴 ID
此方法更适合 B2B 事务。准备附件时,您需要准备 MimeMessage,并插入在第一个 MimeMessage 中插入自定义 Header 的 MimeMultiparts,此自定义 Header 将清楚地说明其内容(应用程序/二进制数据、应用程序/八进制流、应用程序/pkcs7 签名、应用程序/包封数据等等)和合作伙伴的 ID 及其他内容的相关信息。您还需要准备具有以上 MimeMessage 的 javax.activation.DataHandler 对象,然后通过 SOAP/HTTP 发送您创建的 DataHandler。
清单 5 显示了需要对客户端进行的处理:
清单 5. 客户端处理程序
static void sendSignedDocument(AttachmentTip tip,String fileName)
throws java.rmi.RemoteException
{
try{
byte[] crlf=new byte[]{13,10};
ByteArrayOutputStream baos=new ByteArrayOutputStream();
baos.write("Content-Type: ".getBytes());
baos.write("application/pkcs7-signature".getBytes());
baos.write(crlf);
baos.write(crlf);
byte[] ba=baos.toByteArray();
ByteArrayInputStream contentInputs = new ByteArrayInputStream(ba);
MimeMessage m_mimeMessage = new MimeMessage(null, contentInputs);
m_mimeMessage.removeHeader("Content-Type");
MimeMultipart m_mimeMultipart = new MimeMultipart("signed");
ContentType ct = new ContentType(m_mimeMultipart.getContentType());
ct.setParameter("protocol", "application/pkcs7-signature");
ct.setParameter("micalg", "sha1");
// // add the text
InternetHeaders ih=new InternetHeaders();
//
m_mimeMessage.setHeader("Content-Type",ct.toString());
m_mimeMessage.setHeader("Sender-Id","123456789");
m_mimeMessage.setHeader("Receiver-Id","987654321");
//
byte[] sigByteArray =
generateSignature(null,"This is the Temp Data".getBytes());
MimeBodyPart mp = null;
mp=new MimeBodyPart(ih, encode(sigByteArray,"binary")); //"This
is the Temp Data".getBytes());
m_mimeMultipart.addBodyPart(mp);
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
m_mimeMessage.writeTo(byteArray);
m_mimeMultipart.writeTo(byteArray);
MimeBodyPart part = (MimeBodyPart)m_mimeMultipart.getBodyPart(0);
Object partobj = part.getContent();
InputStream is = part.getRawInputStream(); //getContentStream();
byte[] isData = new byte[is.available()];
is.read(isData);
is.close();
java.io.FileOutputStream fos1 =
new java.io.FileOutputStream("c:\\tempout2.txt");
fos1.write(isData);
fos1.close();
java.io.FileOutputStream fos =
new java.io.FileOutputStream("c:\\tempout.txt");
fos.write(byteArray.toByteArray());
fos.close();
FileDataSource fds1 = new FileDataSource("c:\\tempout.txt");
DataHandler dh0 = new DataHandler(fds1);
System.out.println("dh0.getContentType() "+dh0.getContentType());
dh0.getContentType();
FileDataSource fds = new FileDataSource(fileName);
DataHandler dh = new DataHandler(fds);
System.out.println("dh.getContentType() "+dh.getContentType());
tip.sendSignedDocument(dh0);
}catch(Exception ex){
ex.printStackTrace();
}
}
|
清单 6 显示了 generateSignature 方法:
清单 6. generateSignature 方法
public static byte[]
generateSignature(java.security.cert.X509Certificate signingCert,
byte[] messageContent)
throws Exception
{
try
{
PrivateKey ownerSigPrivateKey =
loadPrivateKeysFromPKCS12("C:/
temp/PKITS/signingExpPartner.p12","wpgindia")[0];
PKCS8EncodedKeySpec spec =
new PKCS8EncodedKeySpec(ownerSigPrivateKey.getEncoded());
KeyFactory kf =
KeyFactory.getInstance(ownerSigPrivateKey.getAlgorithm());
PrivateKey JCEPKey = kf.generatePrivate(spec);
SignedData signeddata = null;
byte[] encodedSignedData = null;
String digestAlgo = "sha1";
// SHA1/MD5 is the Message Digest Algo while RSA is the
Signature Algorithm ?? OK.
String signatureAlgorithm = "SHA1withRSA"; // default
if(digestAlgo != null || digestAlgo.equalsIgnoreCase("SHA1"))
signatureAlgorithm = "SHA1withRSA";
Data data = new Data();
data.setData(messageContent);
ContentInfo contentInfo = new ContentInfo(data);
java.security.cert.Certificate[] certs =
new java.security.cert.Certificate[1];
certs[0] = loadX509Certificate("C:/
temp/PKITS/signingExpPartner.der"); //signingCert;
CRL[] crls = null;
PKCSAttributes signedAttributes = null;
PKCSAttributes unsignedAttributes = null;
PrivateKey[] privateKeys = new PrivateKey[1];
privateKeys[0] = JCEPKey;
boolean signatureOnly =
false; //true; // Take false value NOT true, if you
want MessageDigest to be
generated for SingerInfo
// You need to generate SignedData Object with signatureOnly false and
signedAttributes = null || unsignedAttributes = null
// By doing this in SignerInfo(Retrieved from signedData) you will get:
// 1. MessageDigest
// 2. ContentType
// 3. SigningTime
signeddata =
new SignedData(certs,crls,contentInfo,signatureAlgorithm,privateKeys,
signedAttributes,unsignedAttributes,signatureOnly);
ContentInfo contentInfo2 = new ContentInfo(signeddata);
signeddata.removeContent();
encodedSignedData = contentInfo2.encode();
java.io.FileOutputStream fos =
new java.io.FileOutputStream("c:\\tempsig.txt");
fos.write((new String(encodedSignedData)).getBytes());
fos.close();
System.out.println("Generated Signature :"+new String(encodedSignedData));
return encodedSignedData;
}
catch ( Exception e )
{
e.printStackTrace();
throw e;
}
}
|
在服务器端,将从接收到的 DataHanlder 对象提取 MimeMessage,以了解事务的详细信息。有关详细信息,请参阅下面的清单 7。
清单 7. 从 DataHandler 提取 MimeMessage——服务器端
public void sendSignedDocument(javax.activation.DataHandler datahandler)
throws java.rmi.RemoteException
{
try{
MimeMessage mimeMessage = null;
java.io.InputStream is = datahandler.getInputStream();
int start = 0;
int num =0;
byte[] image = new byte[50000];
byte[] temp = new byte[1024];
while ((num = is.read(temp)) > 0){
System.out.println("Count ");
System.arraycopy(temp,0,image,start,num);
start+=num;
}
mimeMessage = new javax.mail.internet.MimeMessage
(javax.mail.Session.getDefaultInstance(new Properties(),null),
new ByteArrayInputStream(image));
MimeMultipart mmp = (MimeMultipart)mimeMessage.getContent();
MimeBodyPart part = (MimeBodyPart)mmp.getBodyPart(0);
Object partobj = part.getContent();
InputStream is1 = part.getRawInputStream(); //getContentStream();
byte[] isData = new byte[is1.available()];
is1.read(isData);
is1.close();
byte[] contents1 = isData;
verifySignature(contents1,"This is the Temp Data".getBytes());
java.io.FileOutputStream fos =
new java.io.FileOutputStream("C:\\output.txt");
fos.write(image);
fos.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
|
清单 8 显示了 VerifySignature 方法:
清单 8. VerifySignature 方法
public static int verifySignature(byte [] signature, byte[] messageContent)
{
int ret=100;
SignerInfo[] signers=null;
SignedData signedData = null;
SignerInfo signer=null;
Properties vcp=null;
X509Certificate cert = null;
try
{
ContentInfo contentInfoEnvData = new ContentInfo(signature);
signedData = (SignedData)contentInfoEnvData.getContent();
cert = loadX509Certificate("C:/temp/PKITS/signingExpPartner.der");
signer = signedData.getSignerInfo(cert);
boolean result = signer.verify(cert,messageContent);
System.out.println("verifySignature result is "+result);
if(result) ret = 100;
else ret = 001;
}
catch (Exception e){
e.printStackTrace();
ret=001;
}
return ret;
}
|
Web 服务描述语言(Web Services Description Language,WSDL)文件必须具有以下结构才能发送附件,如下面的清单 9 所示:
清单 9. WSDL 结构
<operation name="sendSignedDocument">
<soap:operation soapAction=""/>
<input>
<mime:multipartRelated>
<mime:part>
<soap:body use="literal"/>
</mime:part>
<mime:part>
<mime:content part="signedDocContent" type="application/pkcs7-mime"/>
</mime:part>
</mime:multipartRelated>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
|
清单 10 显示了对应的映射文件:
清单 10. 映射文件结构
<service-endpoint-method-mapping>
<java-method-name>sendSignedDocument</java-method-name>
<wsdl-operation>sendSignedDocument</wsdl-operation>
<method-param-parts-mapping>
<param-position>0</param-position>
<param-type>javax.activation.DataHandler</param-type>
<wsdl-message-mapping>
<wsdl-message xmlns:mppm=
"urn:attachment.tip">mppm:signedDcoumentContent</wsdl-message>
<wsdl-message-part-name>signedDocContent</wsdl-message-part-name>
<parameter-mode>IN</parameter-mode>
</wsdl-message-mapping>
</method-param-parts-mapping>
<wsdl-return-value-mapping>
<method-return-value>void</method-return-value>
<wsdl-message xmlns:wrvm=
"urn:attachment.tip">wrvm:empty</wsdl-message>
</wsdl-return-value-mapping>
</service-endpoint-method-mapping>
|
结束语
需要使用上述两种方法来发送纯数据、加密数据、签名数据和压缩数据,或任何格式的带合作伙伴标识的包封数据 (PKCS7)。需要使用这些方法来实现数据的不可否认性和通过 Internet 安全地发送数据。
参考资料 学习
获得产品和技术
- 从 DB2®、Lotus®、Rational、Tivoli® 和 WebSphere® 获得应用程序开发工具和中间件产品。您可以免费下载产品评估版,或者选择 Linux® 或 Windows® 版本的免费 软件评估包。
讨论
关于作者  | 
|  | Vikas K Arora 是一名软件工程师,主要从事 IBM WebSphere Partner Gateway 产品的研究。他的主要编程兴趣是使用 Java 2 Platform Enterprise Edition (J2EE)、Web 服务、O/R 工具和 WebSphere 中的各种新兴技术。您可以通过 vikaaror@in.ibm.com 与他联系。 |
对本文的评价
|  |