可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Can anybody provide me with a code sample to access rest service url secured with https using spring rest template.
I have the certificate, username and password. Basic Authentication is used on the server side and I want to create a client that can connect to that server using provided certificate, username and password (if needed).
回答1:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File(keyStoreFile)), keyStorePassword.toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build(),
NoopHostnameVerifier.INSTANCE);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
MyRecord record = restTemplate.getForObject(uri, MyRecord.class);
LOG.debug(record.toString());
回答2:
Here is some code that will give you the general idea.
You need to create a custom ClientHttpRequestFactory
in order to trust the certificate.
It looks like this:
final ClientHttpRequestFactory clientHttpRequestFactory =
new MyCustomClientHttpRequestFactory(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, serverInfo);
restTemplate.setRequestFactory(clientHttpRequestFactory);
This is the implementation for MyCustomClientHttpRequestFactory
:
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
private final HostnameVerifier hostNameVerifier;
private final ServerInfo serverInfo;
public MyCustomClientHttpRequestFactory (final HostnameVerifier hostNameVerifier,
final ServerInfo serverInfo) {
this.hostNameVerifier = hostNameVerifier;
this.serverInfo = serverInfo;
}
@Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(hostNameVerifier);
((HttpsURLConnection) connection).setSSLSocketFactory(initSSLContext()
.getSocketFactory());
}
super.prepareConnection(connection, httpMethod);
}
private SSLContext initSSLContext() {
try {
System.setProperty("https.protocols", "TLSv1");
// Set ssl trust manager. Verify against our server thumbprint
final SSLContext ctx = SSLContext.getInstance("TLSv1");
final SslThumbprintVerifier verifier = new SslThumbprintVerifier(serverInfo);
final ThumbprintTrustManager thumbPrintTrustManager =
new ThumbprintTrustManager(null, verifier);
ctx.init(null, new TrustManager[] { thumbPrintTrustManager }, null);
return ctx;
} catch (final Exception ex) {
LOGGER.error(
"An exception was thrown while trying to initialize HTTP security manager.", ex);
return null;
}
}
In this case my serverInfo
object contains the thumbprint of the server.
You need to implement the TrustManager
interface to get
the SslThumbprintVerifier
or any other method you want to verify your certificate (you can also decide to also always return true
).
The value org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
allows all host names.
If you need to verify the host name,
you will need to implement it differently.
I'm not sure about the user and password and how you implemented it.
Often,
you need to add a header to the restTemplate
named Authorization
with a value that looks like this: Base: <encoded user+password>
.
The user+password
must be Base64
encoded.
回答3:
This is a solution with no deprecated class or method :
(Java 8 approved)
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
回答4:
Here is what I ended up with for the similar problem. The idea is the same as in @Avi's answer, but I also wanted to avoid the static "System.setProperty("https.protocols", "TLSv1");", so that any adjustments won't affect the system. Inspired by an answer from here http://www.coderanch.com/t/637177/Security/Disabling-handshake-message-Java
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier((hostname, session) -> true);
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
*/
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[] {"SSLv3"});
return socket;
}
}
}
回答5:
One point from me. I used a mutual cert authentication with spring-boot microservices. The following is working for me, keys point here are
keyManagerFactory.init(...) and sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()) lines of code without them atleast for me things did not work. Certificates are packaged by PKCS12.
@Value("${server.ssl.key-store-password}")
private String keyStorePassword;
@Value("${server.ssl.key-store-type}")
private String keyStoreType;
@Value("${server.ssl.key-store}")
private Resource resource;
private RestTemplate getRestTemplate() throws Exception {
return new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
private HttpClient httpClient() throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore trustStore = KeyStore.getInstance(keyStoreType);
if (resource.exists()) {
InputStream inputStream = resource.getInputStream();
try {
if (inputStream != null) {
trustStore.load(inputStream, keyStorePassword.toCharArray());
keyManagerFactory.init(trustStore, keyStorePassword.toCharArray());
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
} else {
throw new RuntimeException("Cannot find resource: " + resource.getFilename());
}
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null, getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
}