Skip to content

Commit 2c650a5

Browse files
committed
SWS-468 - Add a new HttpsUrlConnectionMessageSender implementation to allow customization of certificate management
1 parent d803e1b commit 2c650a5

File tree

7 files changed

+253
-8
lines changed

7 files changed

+253
-8
lines changed

core/src/main/java/org/springframework/ws/transport/TransportException.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ protected TransportException(String msg) {
3030
super(msg);
3131
}
3232

33+
protected TransportException(String msg, Throwable cause) {
34+
super(msg);
35+
initCause(cause);
36+
}
37+
3338
}

core/src/main/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSender.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import java.net.URI;
2020

21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
2124
import org.springframework.ws.transport.WebServiceMessageSender;
2225

2326
/**
@@ -29,6 +32,11 @@
2932
*/
3033
public abstract class AbstractHttpWebServiceMessageSender implements WebServiceMessageSender {
3134

35+
/**
36+
* Logger available to subclasses.
37+
*/
38+
protected final Log logger = LogFactory.getLog(getClass());
39+
3240
private boolean acceptGzipEncoding = true;
3341

3442
/**

core/src/main/java/org/springframework/ws/transport/http/HttpTransportException.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,10 @@ public class HttpTransportException extends TransportException {
2929
public HttpTransportException(String msg) {
3030
super(msg);
3131
}
32+
33+
protected HttpTransportException(String msg, Throwable cause) {
34+
super(msg);
35+
initCause(cause);
36+
}
37+
3238
}

core/src/main/java/org/springframework/ws/transport/http/HttpUrlConnectionMessageSender.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,31 @@ public WebServiceConnection createConnection(URI uri) throws IOException {
4646
}
4747
else {
4848
HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
49-
httpURLConnection.setRequestMethod(HttpTransportConstants.METHOD_POST);
50-
httpURLConnection.setUseCaches(false);
51-
httpURLConnection.setDoInput(true);
52-
httpURLConnection.setDoOutput(true);
53-
if (isAcceptGzipEncoding()) {
54-
httpURLConnection.setRequestProperty(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
55-
HttpTransportConstants.CONTENT_ENCODING_GZIP);
56-
}
49+
prepareConnection(httpURLConnection);
5750
return new HttpUrlConnection(httpURLConnection);
5851
}
5952
}
6053

54+
/**
55+
* Template method for preparing the given {@link java.net.HttpURLConnection}.
56+
* <p/>
57+
* The default implementation prepares the connection for input and output, sets the HTTP method to POST, disables
58+
* caching, and sets the {@code Accept-Encoding} header to gzip, if {@linkplain #setAcceptGzipEncoding(boolean)
59+
* applicable}.
60+
*
61+
* @param connection the connection to prepare
62+
* @throws IOException in case of I/O errors
63+
*/
64+
protected void prepareConnection(HttpURLConnection connection) throws IOException {
65+
connection.setRequestMethod(HttpTransportConstants.METHOD_POST);
66+
connection.setUseCaches(false);
67+
connection.setDoInput(true);
68+
connection.setDoOutput(true);
69+
if (isAcceptGzipEncoding()) {
70+
connection.setRequestProperty(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
71+
HttpTransportConstants.CONTENT_ENCODING_GZIP);
72+
}
73+
}
74+
75+
6176
}

core/src/test/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSenderIntegrationTestCase.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.io.OutputStream;
2121
import java.net.URI;
22+
import java.net.URISyntaxException;
2223
import java.util.zip.GZIPOutputStream;
2324
import javax.servlet.Servlet;
2425
import javax.servlet.ServletException;
@@ -50,6 +51,7 @@
5051
import org.springframework.ws.transport.WebServiceConnection;
5152
import org.springframework.xml.transform.StringResult;
5253
import org.springframework.xml.transform.StringSource;
54+
import org.springframework.beans.factory.InitializingBean;
5355

5456
public abstract class AbstractHttpWebServiceMessageSenderIntegrationTestCase extends XMLTestCase {
5557

@@ -91,6 +93,9 @@ protected final void setUp() throws Exception {
9193
jettyServer = new Server(8888);
9294
jettyContext = new Context(jettyServer, "/");
9395
messageSender = createMessageSender();
96+
if (messageSender instanceof InitializingBean) {
97+
((InitializingBean) messageSender).afterPropertiesSet();
98+
}
9499
XMLUnit.setIgnoreWhitespace(true);
95100
saajMessageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
96101
messageFactory = new SaajSoapMessageFactory(saajMessageFactory);
@@ -105,6 +110,10 @@ protected final void tearDown() throws Exception {
105110
}
106111
}
107112

113+
public void testSupports() throws URISyntaxException {
114+
assertTrue("Message sender does not support HTTP url", messageSender.supports(new URI(URI_STRING)));
115+
}
116+
108117
public void testSendAndReceiveResponse() throws Exception {
109118
MyServlet servlet = new MyServlet();
110119
servlet.setResponse(true);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2009 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ws.transport.http;
18+
19+
/**
20+
* Exception that is thrown when an error occurs in the HTTP transport.
21+
*
22+
* @author Arjen Poutsma
23+
* @since 1.5.8
24+
*/
25+
public class HttpsTransportException extends HttpTransportException {
26+
27+
public HttpsTransportException(String msg) {
28+
super(msg);
29+
}
30+
31+
public HttpsTransportException(String msg, Throwable cause) {
32+
super(msg, cause);
33+
}
34+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2002-2009 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ws.transport.http;
18+
19+
import java.io.IOException;
20+
import java.net.HttpURLConnection;
21+
import java.net.URI;
22+
import java.security.GeneralSecurityException;
23+
import java.security.KeyManagementException;
24+
import java.security.NoSuchAlgorithmException;
25+
import java.security.NoSuchProviderException;
26+
import java.security.SecureRandom;
27+
import java.util.Arrays;
28+
import javax.net.ssl.HostnameVerifier;
29+
import javax.net.ssl.HttpsURLConnection;
30+
import javax.net.ssl.KeyManager;
31+
import javax.net.ssl.SSLContext;
32+
import javax.net.ssl.TrustManager;
33+
34+
import org.springframework.beans.factory.InitializingBean;
35+
import org.springframework.util.Assert;
36+
import org.springframework.util.ObjectUtils;
37+
import org.springframework.util.StringUtils;
38+
39+
/**
40+
* Extension of {@link HttpUrlConnectionMessageSender} that adds support for (self-signed) HTTPS certificates.
41+
*
42+
* @author Alex Marshall
43+
* @author Arjen Poutsma
44+
* @since 1.5.8
45+
*/
46+
public class HttpsUrlConnectionMessageSender extends HttpUrlConnectionMessageSender implements InitializingBean {
47+
48+
/** The default SSL protocol. */
49+
public static final String DEFAULT_SSL_PROTOCOL = "ssl";
50+
51+
private String sslProtocol = DEFAULT_SSL_PROTOCOL;
52+
53+
private String sslProvider;
54+
55+
private KeyManager[] keyManagers;
56+
57+
private TrustManager[] trustManagers;
58+
59+
private HostnameVerifier hostnameVerifier;
60+
61+
private SecureRandom rnd;
62+
63+
/**
64+
* Sets the SSL protocol to use. Default is {@code ssl}.
65+
*
66+
* @see SSLContext#getInstance(String, String)
67+
*/
68+
public void setSslProtocol(String sslProtocol) {
69+
Assert.hasLength(sslProtocol, "'sslProtocol' must not be empty");
70+
this.sslProtocol = sslProtocol;
71+
}
72+
73+
/**
74+
* Sets the SSL provider to use. Default is empty, to use the default provider.
75+
*
76+
* @see SSLContext#getInstance(String, String)
77+
*/
78+
public void setSslProvider(String sslProvider) {
79+
this.sslProvider = sslProvider;
80+
}
81+
82+
/**
83+
* Specifies the key managers to use for this message sender.
84+
* <p/>
85+
* Setting either this property or {@link #setTrustManagers(TrustManager[]) trustManagers} is required.
86+
*
87+
* @see SSLContext#init(KeyManager[], TrustManager[], SecureRandom)
88+
*/
89+
public void setKeyManagers(KeyManager[] keyManagers) {
90+
this.keyManagers = keyManagers;
91+
}
92+
93+
/**
94+
* Specifies the trust managers to use for this message sender.
95+
* <p/>
96+
* Setting either this property or {@link #setKeyManagers(KeyManager[]) keyManagers} is required.
97+
*
98+
* @see SSLContext#init(KeyManager[], TrustManager[], SecureRandom)
99+
*/
100+
public void setTrustManagers(TrustManager[] trustManagers) {
101+
this.trustManagers = trustManagers;
102+
}
103+
104+
/**
105+
* Specifies the host name verifier to use for this message sender.
106+
*
107+
* @see HttpsURLConnection#setHostnameVerifier(HostnameVerifier)
108+
*/
109+
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
110+
this.hostnameVerifier = hostnameVerifier;
111+
}
112+
113+
/**
114+
* Specifies the secure random to use for this message sender.
115+
*
116+
* @see SSLContext#init(KeyManager[], TrustManager[], SecureRandom)
117+
*/
118+
public void setSecureRandom(SecureRandom rnd) {
119+
this.rnd = rnd;
120+
}
121+
122+
public void afterPropertiesSet() throws Exception {
123+
Assert.isTrue(!(ObjectUtils.isEmpty(keyManagers) && ObjectUtils.isEmpty(trustManagers)),
124+
"Setting either 'keyManagers' or 'trustManagers' is required");
125+
}
126+
127+
protected void prepareConnection(HttpURLConnection connection) throws IOException {
128+
super.prepareConnection(connection);
129+
if (connection instanceof HttpsURLConnection) {
130+
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
131+
try {
132+
SSLContext sslContext = createSslContext(sslProtocol, sslProvider);
133+
sslContext.init(keyManagers, trustManagers, rnd);
134+
if (logger.isDebugEnabled()) {
135+
logger.debug("Initialized SSL Context with key managers [" +
136+
StringUtils.arrayToCommaDelimitedString(keyManagers) + "] trust managers [" +
137+
StringUtils.arrayToCommaDelimitedString(trustManagers) + "] secure random [" + rnd + "]");
138+
}
139+
140+
httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory());
141+
142+
if (hostnameVerifier != null) {
143+
httpsConnection.setHostnameVerifier(hostnameVerifier);
144+
}
145+
}
146+
catch (NoSuchProviderException ex) {
147+
throw new HttpsTransportException("Could not create SSLContext: " + ex.getMessage(), ex);
148+
}
149+
catch (NoSuchAlgorithmException ex) {
150+
throw new HttpsTransportException("Could not create SSLContext: " + ex.getMessage(), ex);
151+
}
152+
catch (KeyManagementException ex) {
153+
throw new HttpsTransportException("Could not initialize SSLContext: " + ex.getMessage(), ex);
154+
}
155+
}
156+
}
157+
158+
private SSLContext createSslContext(String protocol, String provider)
159+
throws NoSuchProviderException, NoSuchAlgorithmException {
160+
if (!StringUtils.hasLength(provider)) {
161+
return SSLContext.getInstance(protocol);
162+
}
163+
else {
164+
return SSLContext.getInstance(protocol, provider);
165+
}
166+
}
167+
168+
}

0 commit comments

Comments
 (0)