webservice复杂加密签名(2)java调用

依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-ws</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>

WSDL转java

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.2</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generateDirectory>${basedir}/src/main/java</generateDirectory>
<schemas>
<schema>
<fileset>
<directory>${basedir}/src/main/resources/wsdl</directory>
<includes>
<include>*.wsdl</include>
<include>*.wsd</include>
</includes>
</fileset>
</schema>
</schemas>
</configuration>
</plugin>
</plugins>
</build>
1
mvn org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.13.2:generate

image

HTTPS签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.ws.transport.WebServiceMessageSender;
import org.springframework.ws.transport.http.ClientHttpRequestMessageSender;

import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyStore;

@Slf4j
@Configuration
public class SoapConfig {

@Value("${mastercard.keystore.client.path}")
private String clientKeystorePath;
@Value("${mastercard.keystore.client.password}")
private String clientPassword;

@Bean
public WebServiceMessageSender webServiceMessageSender() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
byte[] keystoreBytes;
try (InputStream inputStream = resolver.getResource(clientKeystorePath).getInputStream()) {
keystoreBytes = IOUtils.toByteArray(inputStream);
} catch (Exception e) {
log.error("https秘钥文件client.jks读取异常, keystore: {}", clientKeystorePath);
throw e;
}

KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new ByteArrayInputStream(keystoreBytes), clientPassword.toCharArray());

SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keystore, clientPassword.toCharArray())
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectTimeout(10_000);
requestFactory.setReadTimeout(10_000);
ClientHttpRequestMessageSender messageSender = new ClientHttpRequestMessageSender();
messageSender.setRequestFactory(requestFactory);
return messageSender;
}

}

报文签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.soap.saaj.SaajSoapMessage;

import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.soap.*;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;

@Slf4j
@Component
public class SoapSignService {

private final KeyStore keystore;
private final String password;
private final String keystoreAlias;

@Value("${mastercard.system.institutionName}")
private String institutionName;

@Autowired
public SoapSignService(@Value("${mastercard.keystore.sign.path}") String path,
@Value("${mastercard.keystore.sign.password}") String password,
@Value("${mastercard.keystore.sign.alias}") String keystoreAlias) throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
byte[] keystoreBytes;
try (InputStream inputStream = resolver.getResource(path).getInputStream()) {
keystoreBytes = IOUtils.toByteArray(inputStream);
} catch (Exception e) {
log.error("签名秘钥文件signing.jks读取异常, keystore: {}", path);
throw e;
}

this.keystore = KeyStore.getInstance("PKCS12");
this.keystore.load(new ByteArrayInputStream(keystoreBytes), password.toCharArray());
this.password = password;
this.keystoreAlias = keystoreAlias;
}

private PrivateKey getKeyFormCert() throws Exception {
return (PrivateKey) keystore.getKey(keystoreAlias, password.toCharArray());
}

private java.security.cert.Certificate getCertificate() throws Exception {
return keystore.getCertificate(keystoreAlias);
}

public void sign(WebServiceMessage message) {
sign("", message);
}

public void sign(String appId, WebServiceMessage message) {
this.sign(appId, ((SaajSoapMessage) message).getSaajMessage());
}

public void sign(String appId, SOAPMessage soapMessage) {
try {
SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
soapEnvelope.setPrefix("soapenv");
soapEnvelope.removeNamespaceDeclaration("SOAP-ENV");
soapEnvelope.addNamespaceDeclaration("com", "http://common.ws.mcrewards.mastercard.com/");

SOAPHeader header = soapMessage.getSOAPHeader();
header.setPrefix("soapenv");
SOAPElement identity = header.addChildElement("identity", "com");
identity.addAttribute(soapEnvelope.createName("Id", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"), "IDENTITY");

SOAPElement appID = identity.addChildElement("appID");
appID.addTextNode(appId);
SOAPElement institutionName = identity.addChildElement("institutionName");
institutionName.addTextNode(this.institutionName);

SOAPBody soapBody = soapMessage.getSOAPBody();
soapBody.setPrefix("soapenv");
soapBody.addAttribute(soapEnvelope.createName("Id", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"), "Body");

signSOAPMessage(soapMessage);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}

private void signSOAPMessage(SOAPMessage soapMessage) throws Exception {
// Create the security element
SOAPElement soapHeader = soapMessage.getSOAPHeader();
SOAPElement securityElement = soapHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
securityElement.addNamespaceDeclaration("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

// (i) Extract the certificate from the .p12 file.
java.security.cert.Certificate cert = getCertificate();

// (ii) Add Binary Security Token. The base64 encoded value of the ROS digital certificate.
SOAPElement binarySecurityToken = addBinarySecurityToken(securityElement, soapMessage, cert);

//(iii) Add Timestamp element
SOAPElement timestamp = addTimestamp(securityElement, soapMessage);

// (iv) Add signature element
// Get private key from ROS digital certificate
PrivateKey key = getKeyFormCert();
SOAPElement securityTokenReference = addSecurityToken(securityElement);
addSignature(securityElement, key, securityTokenReference, soapMessage.getSOAPBody(), timestamp, binarySecurityToken);
}

private SOAPElement addSecurityToken(SOAPElement signature) throws SOAPException {
SOAPElement securityTokenReference = signature.addChildElement("SecurityTokenReference", "wsse");
SOAPElement reference = securityTokenReference.addChildElement("Reference", "wsse");
reference.setAttribute("URI", "#X509Token");
return securityTokenReference;
}

private void addSignature(SOAPElement signatureElement, PrivateKey privateKey, SOAPElement securityTokenReference, SOAPBody soapBody, SOAPElement timestamp, SOAPElement binarySecurityToken) throws Exception {
String providerName = System.getProperty("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM", (Provider) Class.forName(providerName).newInstance());

//Digest method
javax.xml.crypto.dsig.DigestMethod digestMethod = xmlSignatureFactory.newDigestMethod("http://www.w3.org/2001/04/xmlenc#sha512", null);
ArrayList<Transform> transformList = new ArrayList<>();

//Transform
Transform envTransform = xmlSignatureFactory.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (TransformParameterSpec) null);
transformList.add(envTransform);

//References
ArrayList<Reference> referenceList = new ArrayList<>();
Reference x509 = xmlSignatureFactory.newReference("#X509Token", digestMethod, transformList, null, null);
Reference refTS = xmlSignatureFactory.newReference("#TS", digestMethod, transformList, null, null);
Reference refBody = xmlSignatureFactory.newReference("#Body", digestMethod, transformList, null, null);
Reference header = xmlSignatureFactory.newReference("#IDENTITY", digestMethod, transformList, null, null);
referenceList.add(x509);
referenceList.add(refTS);
referenceList.add(refBody);
referenceList.add(header);

javax.xml.crypto.dsig.CanonicalizationMethod cm = xmlSignatureFactory.newCanonicalizationMethod("http://www.w3.org/2001/10/xml-exc-c14n#", (C14NMethodParameterSpec) null);
javax.xml.crypto.dsig.SignatureMethod sm = xmlSignatureFactory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", null);
SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(cm, sm, referenceList);


DOMSignContext signContext = new DOMSignContext(privateKey, signatureElement);
signContext.setDefaultNamespacePrefix("ds");
signContext.putNamespacePrefix("http://www.w3.org/2000/09/xmldsig#", "ds");

//These are required for new Java versions
signContext.setIdAttributeNS(soapBody, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id");
signContext.setIdAttributeNS(timestamp, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id");
signContext.setIdAttributeNS(binarySecurityToken, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id");

KeyInfoFactory keyFactory = KeyInfoFactory.getInstance();
DOMStructure domKeyInfo = new DOMStructure(securityTokenReference);
javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo = keyFactory.newKeyInfo(java.util.Collections.singletonList(domKeyInfo));
javax.xml.crypto.dsig.XMLSignature signature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo);
signContext.setBaseURI("");
signature.sign(signContext);
}

private SOAPElement addTimestamp(SOAPElement securityElement, SOAPMessage soapMessage) throws SOAPException {
SOAPElement timestamp = securityElement.addChildElement("Timestamp", "wsu");
SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
timestamp.addAttribute(soapEnvelope.createName("Id", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"), "TS");

String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssX";
DateTimeFormatter timeStampFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
ZonedDateTime zonedDateTime = ZonedDateTime.now();
timestamp.addChildElement("Created", "wsu").setValue(timeStampFormatter.format(zonedDateTime.toInstant().atZone(ZoneId.of("UTC"))));
timestamp.addChildElement("Expires", "wsu").setValue(timeStampFormatter.format(zonedDateTime.plusSeconds(60).toInstant().atZone(ZoneId.of("UTC"))));
return timestamp;
}

private SOAPElement addBinarySecurityToken(SOAPElement securityElement, SOAPMessage soapMessage, java.security.cert.Certificate cert) throws Exception {
byte[] certByte = cert.getEncoded();
SOAPElement binarySecurityToken = securityElement.addChildElement("BinarySecurityToken", "wsse");

binarySecurityToken.setAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
binarySecurityToken.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();

binarySecurityToken.addAttribute(soapEnvelope.createName("Id", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"), "X509Token");
binarySecurityToken.addTextNode(Base64.getEncoder().encodeToString(certByte));
return binarySecurityToken;
}

}

调用层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import com.mastercard.mcrewards.ws.common.EmptyElement;
import com.mastercard.mcrewards.ws.diagnostic.ApplicationStatus;
import com.mastercard.mcrewards.ws.diagnostic.CurrentVersion;
import com.mastercard.mcrewards.ws.diagnostic.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.transport.WebServiceMessageSender;

import javax.xml.bind.JAXBElement;

@Service
public class SoapDiagnosticService extends WebServiceGatewaySupport {

private static final ObjectFactory factory = new com.mastercard.mcrewards.ws.diagnostic.ObjectFactory();
private static final String packagePath = "com.mastercard.mcrewards.ws.diagnostic";

private SoapSignService soapSignService;

@Autowired
public SoapDiagnosticService(WebServiceMessageSender messageSender,
SoapSignService soapSignService,
@Value("${mastercard.service.diagnosticServiceLink}") String link) {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath(packagePath);
this.setDefaultUri(link);
this.setMarshaller(marshaller);
this.setUnmarshaller(marshaller);
this.setMessageSender(messageSender);
this.soapSignService = soapSignService;
}

public String doEcho(String request) {
JAXBElement<String> source = factory.createDoEcho(request);
WebServiceMessageCallback wsCallback = message -> soapSignService.sign(message);
JAXBElement response = (JAXBElement) getWebServiceTemplate().marshalSendAndReceive(source, wsCallback);
return (String) response.getValue();
}

}
>