使用 Spring RestTemplate 访问 Https Rest 服务
Posted
技术标签:
【中文标题】使用 Spring RestTemplate 访问 Https Rest 服务【英文标题】:Access Https Rest Service using Spring RestTemplate 【发布时间】:2013-07-11 06:55:50 【问题描述】:谁能提供一个代码示例来访问使用 Spring Rest 模板通过 HTTPS 保护的 REST 服务 URL?
我有证书、用户名和密码。基本身份验证在服务器端使用,我想创建一个可以使用提供的证书、用户名和密码(如果需要)连接到该服务器的客户端。
【问题讨论】:
【参考方案1】:这是一个不推荐使用类或方法的解决方案: (Java 8 已获批准)
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
重要信息:使用 NoopHostnameVerifier 存在安全风险
【讨论】:
任何时候忽略证书都是安全风险。我假设(希望?)OP 计划使用它进行测试【参考方案2】:您需要配置一个支持 SSL 的原始 HttpClient,如下所示:
@Test
public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect()
throws ClientProtocolException, IOException
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
ResponseEntity<String> response
= new RestTemplate(requestFactory).exchange(
urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
from: Baeldung
【讨论】:
【参考方案3】: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());
【讨论】:
在 5 年春季有没有想出如何做同样的事情 你如何在春季5实现这一目标? 这是完全不安全的。没有服务器证书验证 (TrustSelfSignedStrategy
),没有主机名验证 (NoopHostnameVerifier
)。提供的关键材料用于客户端证书身份验证,但问题说它使用的是 HTTP Basic 身份验证。在这种情况下,从密钥库加载的内容根本不会被使用。【参考方案4】:
从我这里得到一点。我在 spring-boot 微服务中使用了相互证书身份验证。以下对我有用,这里的重点是
keyManagerFactory.init(...)
和 sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom())
没有它们的代码行,至少对我来说,事情没有奏效。证书由 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();
【讨论】:
如果您将trustStore
重命名为keyStore
可能会更清楚,因为您将它用于密钥管理器,而不是信任管理器。【参考方案5】:
这里有一些代码可以让你大致了解一下。
您需要创建自定义ClientHttpRequestFactory
才能信任证书。
它看起来像这样:
final ClientHttpRequestFactory clientHttpRequestFactory =
new MyCustomClientHttpRequestFactory(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, serverInfo);
restTemplate.setRequestFactory(clientHttpRequestFactory);
这是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;
在这种情况下,我的serverInfo
对象包含服务器的指纹。
需要实现TrustManager
接口才能得到
SslThumbprintVerifier
或您想要验证证书的任何其他方法(您也可以决定也始终返回 true
)。
值org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
允许所有主机名。
如果需要验证主机名,
你需要以不同的方式实现它。
我不确定用户和密码以及您是如何实现的。
经常,
您需要将标题添加到名为 Authorization
的 restTemplate
具有如下所示的值:Base: <encoded user+password>
。
user+password
必须是 Base64
编码的。
【讨论】:
我的很多代码都取自这里:***.com/questions/15544116/… 看起来很干净,除了 "System.setProperty("https.protocols", "TLSv1");"线。有没有办法不设置一些系统级的东西?我遇到了类似的问题,想将与证书相关的问题隔离到一个 bean。 @Ruslan - 哇,你用这个答案把我带回来了。不幸的是,那是很久以前的事了,从那时起我已经换了两个工作场所,所以我没有源代码,我不记得为什么我会这样做。我很确定有办法解决它,我会尝试看看是否可以找到另一种方法,如果可以,我会在这里发布。 我已经找到了解决方案。让我用它发布另一个答案,因为它不是微不足道的。无论如何,谢谢。 你最好加上SslThumbprintVerifier
class。【参考方案6】:
这是我对类似问题的最终结果。这个想法与@Avi的答案相同,但我也想避免静态“System.setProperty("https.protocols", "TLSv1");",这样任何调整都不会影响系统。灵感来自这里的答案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;
【讨论】:
不安全,因为没有证书验证和主机名验证。以上是关于使用 Spring RestTemplate 访问 Https Rest 服务的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 RestTemplate 在 Spring MVC 应用程序中访问来自(来自 Spring RESTful 服务)的巨大 JSON
(030)Spring Boot之RestTemplate访问web服务案例