Access Https Rest Service using Spring RestTemplate - spring

Can anybody provide me with a code sample to access the rest service URL secured with HTTPS using the 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 a provided certificate, username and password (if needed).

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());

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.

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);
Important information : Using NoopHostnameVerifier is a security risk

One point from me. I used a mutual cert authentication with spring-boot microservices. The following is working for me, key points here are
keyManagerFactory.init(...) and sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()) lines of code without them, at least 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();
}

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;
}
}
}

You need to configure a raw HttpClient with SSL support, something like this:
#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

Related

Configure SSL with Webflux Webclient using Apache HttpComponents

I am trying to migrate from restTemplate to webClient.
Everything was fine until I reached restTemplate config with ClientHttpRequestFactory.
I paste here the old and the new codes.
------Old code with restTemplate-------
private HttpComponentsClientHttpRequestFactory buildRequestFactory() {
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUser, proxyPassword));
clientBuilder.useSystemProperties();
clientBuilder.setProxy(proxy);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ServiceException(GlobalErrorMessage.INTERNAL_SERVER_ERROR);
}
SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = clientBuilder
.setSSLSocketFactory(connectionFactory)
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals(noproxy)) {
return null;
}
return super.determineProxy(target, request, context);
}
})
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return requestFactory;
}
#Bean(name = "gatewayRestTemplate")
public RestTemplate gatewayRestTemplateConfig() {
RestTemplate restTemplate = new RestTemplate(converters());
restTemplate.setRequestFactory(buildRequestFactory());
return restTemplate;
}
------New code with webClient-------
private ClientHttpConnector buildClientConnector() {
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
org.apache.hc.core5.http.HttpHost proxy = new org.apache.hc.core5.http.HttpHost(proxyHost, proxyPort);
org.apache.hc.client5.http.auth.CredentialsProvider credsProvider = new org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider();
((org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider) credsProvider).setCredentials(new org.apache.hc.client5.http.auth.AuthScope(proxyHost, proxyPort),
new org.apache.hc.client5.http.auth.UsernamePasswordCredentials(proxyUser, proxyPassword.toCharArray()));
clientBuilder.useSystemProperties();
clientBuilder.setProxy(proxy);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
clientBuilder.setProxyAuthenticationStrategy(new DefaultAuthenticationStrategy());
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ServiceException(GlobalErrorMessage.INTERNAL_SERVER_ERROR);
}
org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory connectionFactory =
new org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
org.apache.hc.core5.http.config.Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
// .<org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory>create().register("https", connectionFactory)
.<ConnectionSocketFactory>create().register("https", connectionFactory)
// .register("http", new PlainConnectionSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpAsyncClient client = clientBuilder
.setConnectionManager((AsyncClientConnectionManager) connectionManager)
.setRoutePlanner(new org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner(proxy) {
#Override
protected org.apache.hc.core5.http.HttpHost determineProxy(org.apache.hc.core5.http.HttpHost target, org.apache.hc.core5.http.protocol.HttpContext context) throws org.apache.hc.core5.http.HttpException {
if (target.getHostName().equals(noproxy)) {
return null;
}
return super.determineProxy(target, context);
}
})
.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
return connector;
}
#Primary
#Bean(name = "defaultWebClient")
public WebClient defaultWebClientConfig() {
WebClient webClient = WebClient.builder()
.clientConnector(buildClientConnector())
.build();
return webClient;
}
When I run the project, I get this exception:
Caused by: java.lang.ClassCastException: class org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager cannot be cast to class org.apache.hc.client5.http.nio.AsyncClientConnectionManager (org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager and org.apache.hc.client5.http.nio.AsyncClientConnectionManager are in unnamed module of loader 'app')
Based on Migration to Apache HttpClient 5.0 async APIs, I solved my problem. The idea is to use ClientTlsStrategyBuilder when setting sslContext.
private ClientHttpConnector buildClientConnector() {
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
org.apache.hc.core5.http.HttpHost proxy = new org.apache.hc.core5.http.HttpHost(proxyHost, proxyPort);
org.apache.hc.client5.http.auth.CredentialsProvider credsProvider = new org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider();
((org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider) credsProvider).setCredentials(new org.apache.hc.client5.http.auth.AuthScope(proxyHost, proxyPort),
new org.apache.hc.client5.http.auth.UsernamePasswordCredentials(proxyUser, proxyPassword.toCharArray()));
clientBuilder.useSystemProperties();
clientBuilder.setProxy(proxy);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
clientBuilder.setProxyAuthenticationStrategy(new DefaultAuthenticationStrategy());
TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ServiceException(GlobalErrorMessage.INTERNAL_SERVER_ERROR);
}
PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsStrategy(ClientTlsStrategyBuilder.create()
.setSslContext(sslContext)
.setHostnameVerifier(new NoopHostnameVerifier())
.build())
.build();
CloseableHttpAsyncClient client = clientBuilder
.setConnectionManager(connectionManager)
.setRoutePlanner(new org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner(proxy) {
#Override
protected org.apache.hc.core5.http.HttpHost determineProxy(org.apache.hc.core5.http.HttpHost target, org.apache.hc.core5.http.protocol.HttpContext context) throws org.apache.hc.core5.http.HttpException {
if (target.getHostName().equals(noproxy)) {
return null;
}
return super.determineProxy(target, context);
}
})
.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
return connector;
}
#Primary
#Bean(name = "defaultWebClient")
public WebClient defaultWebClientConfig() {
WebClient webClient = WebClient.builder()
.clientConnector(buildClientConnector())
.build();
return webClient;
}
If you want to use HttpClient connector. Please use below code for
webclient. The above answers any of them not worked, below solution is
working fine for me.
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create().secure(t ->
t.sslContext(sslContext) );
WebClient webClient = WebClient.builder()
.baseUrl("any-url")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

RestTemplate to call GET HTTPS Endpoint issue

I tried to call the rest endpoint from browser and is working fine, but with rest template, i am not getting any response.
URL: https://www1.nseindia.com/marketinfo/sym_map/symbolCount.jsp?symbol=INFY
public class Test {
public static void main(String[] args) throws Exception {
try {
String jksPath = "C:\\ssl_server.jks";
String pass = "123456";
SSLContext ssl = SSLContextBuilder.create()
.loadTrustMaterial(ResourceUtils.getFile(jksPath), pass.toCharArray()).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(ssl, new LHVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectionRequestTimeout(15000);
requestFactory.setReadTimeout(15000);
System.out.println("Start: " + new Date());
HttpHeaders headers = new HttpHeaders();
headers.set(org.apache.http.HttpHeaders.ACCEPT, "*/*");
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
ResponseEntity<Object> response = new RestTemplate(requestFactory).exchange(
"https://www1.nseindia.com/marketinfo/sym_map/symbolCount.jsp?symbol=INFY", HttpMethod.GET,
httpEntity, Object.class);
} catch (Exception e) {
System.out.println("Exception: " + new Date());
e.printStackTrace();
}
System.out.println("done");
}
}
class LHVerifier implements HostnameVerifier {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}

Post resttemplate in Spring does not work and get works

This is my client code:
#GetMapping("/")
public String home() throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyStoreException, KeyManagementException, UnrecoverableKeyException, RestClientException, URISyntaxException {
String url = "https://localhost:8483/secure-server/hola";
//
// KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
//
// keyStore.load(new FileInputStream(new File("client-keystore.jks")), "secret".toCharArray());
//
// System.out.println(url);
// SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
// new SSLContextBuilder()
// .loadTrustMaterial(null, new TrustSelfSignedStrategy())
// .loadKeyMaterial(keyStore, "secret".toCharArray())
// .build(),
// NoopHostnameVerifier.INSTANCE);
//
// HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
//
// ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
// RestTemplate restTemplate = new RestTemplate(requestFactory);
// String record = restTemplate.getForObject(url, String.class);
////
//
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<>("", headers);
return restTemplate.exchange(url, HttpMethod.POST, request , String.class ).getBody();
//
// ResponseEntity<String> resp = restTemplate.exchange(
// new URI(url), HttpMethod.GET,
// httpEntity, String.class);
//return model.getBody();
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
char[] password = "secret".toCharArray();
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(keyStore("client-keystore.jks", password), password)
.loadTrustMaterial(new File("client-truststore.jks"),"secret".toCharArray()).build();
HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
return builder
.requestFactory(new HttpComponentsClientHttpRequestFactory(client))
.build();
}
private KeyStore keyStore(String file, char[] password) throws Exception {
KeyStore keyStore = KeyStore.getInstance("jks");
File key = ResourceUtils.getFile(file);
try (InputStream in = new FileInputStream(key)) {
keyStore.load(in, password);
}
return keyStore;
}
This is my server code with the two methods post and get, the get is working but post is not working:
#RestController
public class HomeRestController {
#PostMapping("/hola")
public String home(Principal principal) {
return String.format("Hello %s!", principal.getName());
}
#GetMapping("/holaa")
public String homee(Principal principal) {
return String.format("Hello %s!", principal.getName());
}
}
I have this is my YML with the mutual authentication configuration:
server:
context-path: /${spring.application.name}
port: 8483
ssl:
key-store: server-keystore.keystore
key-store-password: pass123
key-alias: default
trust-store: server-truststore.jks
trust-store-password: secret
enabled: true
client-auth: need
Calling the getMaping it works, but calling the postMaping it returns to me 403.
The keystore and trustore are configured and are OK.
And in my security configuration I have:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)").userDetailsService(userDetailsService());
}
#Override
#Bean
public UserDetailsService userDetailsService() {
return (username -> {
return new User(username, "",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
});
}
}
Why my post calling does not work?

Getting TestRestTemplate to work with https

Writing JUnit Integrtaion tests for a REST endpoint which sets secure cookies, can't get past the ResourceAccessException error.
Requirement is to do a https://localhost:8443 request.
Have tried using the customRestTemplate
Getting the folloiwng exception.
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://localhost:8443/dcs": Connect to localhost:8443 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException
Below is the code.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DcsServiceTests {
#Autowired
RestTemplateBuilder restTemplateBuilder;
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testGet_ImageResponse() throws Exception {
//Arrange
//Act
ResponseEntity<byte[]> response = testRestTemplate.getForEntity(url, byte[].class);
//Assert
//Response Status
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
//Response has cookie
assertThat(response.getHeaders().containsKey("Set-Cookie")).isTrue();
}
#PostConstruct
public void initialize() {
// Lambda expression not working, TBD - Java version used.
//TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
final TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
return true;
}
};
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
try {
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
requestFactory.setHttpClient(httpClient);
}
catch (Exception e) {
System.out.println("Exception occured creating Request Factory");
}
RestTemplate customTemplate = restTemplateBuilder
.requestFactory(requestFactory)
.rootUri("https://localhost:8443")
.build();
this.testRestTemplate = new TestRestTemplate(
customTemplate,
null,
null, // Not using basic auth
TestRestTemplate.HttpClientOption.ENABLE_COOKIES); // Cookie support
}
}
Disabling SSL and then using testRestTemplate with exchange method worked. Secured cookies works as well, just that the headers needs to be parsed to validate results in Unit test cases
#Bean
public Boolean disableSSLValidation() throws Exception {
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
} }, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return true;
}
public void hostNameVerifier() {
final HostnameVerifier defaultHostnameVerifier = javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier ();
final HostnameVerifier localhostAcceptedHostnameVerifier = new javax.net.ssl.HostnameVerifier () {
public boolean verify ( String hostname, javax.net.ssl.SSLSession sslSession ) {
if ( hostname.equals ( "localhost" ) ) {
return true;
}
return defaultHostnameVerifier.verify ( hostname, sslSession );
}
};
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier ( localhostAcceptedHostnameVerifier );
}
#Test
public void testGet_ImageResponse() throws Exception {
//Arrange
String url = getUrl() + "/xyz?s_action=test&s_type=i";
//Act
ResponseEntity<byte[]> response = restTemplate.getForEntity(url, byte[].class);
//Assert
//Response Status
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
//Response has cookie
assertThat(response.getHeaders().containsKey("Set-Cookie")).isTrue();
//Extract cookie from header
List<String> cookies = response.getHeaders().get("Set-Cookie");
//Construct cookie from RAW Header Response
Cookie cookie = RawCookieParser.constructCookieFromHeaderResponse(response.getHeaders().get("Set-Cookie").toString());
//Cookies name matches
//Cookie value cannot be matched because value is being set from external JAR
assertEquals(cookie.getName(), appConfig.getName());
//Cookie domain matches
assertEquals(cookie.getDomain(), appConfig.getDomain());
}
public class RawCookieParser {
/*
* Construct a cookie object by parsing the HTTP Header response
*/
public static Cookie constructCookieFromHeaderResponse(String input) throws Exception {
String rawCookie = input.replace("[", "").replace("]", "");
String[] rawCookieParams = rawCookie.split(";");
String[] rawCookieNameAndValue = rawCookieParams[0].split("=");
if (rawCookieNameAndValue.length != 2) {
throw new Exception("Invalid cookie: missing name and value.");
}
String cookieName = rawCookieNameAndValue[0].trim();
String cookieValue = rawCookieNameAndValue[1].trim();
Cookie cookie = new Cookie(cookieName, cookieValue);
for (int i = 1; i < rawCookieParams.length; i++) {
String rawCookieParamNameAndValue[] = rawCookieParams[i].trim().split("=");
String paramName = rawCookieParamNameAndValue[0].trim();
if (rawCookieParamNameAndValue.length == 2) {
String paramValue = rawCookieParamNameAndValue[1].trim();
if (paramName.equalsIgnoreCase("secure")) {
cookie.setSecure(true);
} else if (paramName.equalsIgnoreCase("max-age")) {
int maxAge = Integer.parseInt(paramValue);
cookie.setMaxAge(maxAge);
} else if (paramName.equalsIgnoreCase("domain")) {
cookie.setDomain(paramValue);
} else if (paramName.equalsIgnoreCase("path")) {
cookie.setPath(paramValue);
}
}
}
return cookie;
}
}

Spring data mongodb, how to set SSL?

I have so far failed to find a good explanation/doc on the topic.
I am using
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.9.5.RELEASE</version>
</dependency>
and my code looks like this:
#Bean
public MongoClientFactoryBean mongo() {
MongoClientFactoryBean mongo = new MongoClientFactoryBean();
mongo.setHost(host);
mongo.setPort(port);
mongo.setCredentials(new MongoCredential[]{MongoCredential.createCredential(username, database, password.toCharArray())});
return mongo;
}
#Bean
public MongoTemplate mongoTemplate(Mongo mongo) throws Exception {
return new MongoTemplate(mongo, database);
}
Do you know how I should configure SSL for this? And can I allow invalid certificate?
The equivalent mongo command line would be
mongo --ssl --sslAllowInvalidCertificates --host <host> --port <port>
If you just want to connect your spring boot app with mongodb, you can use the keyStore and trustStore with java code. So you dont have to add your certificate via command line. If you are using cloud foundry you can connect your app with mongodbServices and then you have all the credentials you need in System.getEnv("VCAP_SERVICES").
#Configuration
public class MongoConfiguration extends AbstractMongoConfiguration {
private static Log logger = LogFactory.getLog(MongoConfiguration.class);
#Value("${spring.data.mongodb.database}")
private String defaultDatabase; //database you want to connect
private String host;
private int port;
private String authenticationDb; //usually admin
private String username;
private char[] password;
private String certificateDecoded; //your CA Certifcate decoded (starts with BEGIN CERTIFICATE)
public MongoConfiguration() {
//method for credentials initialization
}
//you can't set replicaset=replset in mongooptions so if you want set replicaset, you have to use
// customEditorConfigurer in combintaion with class that implementsPropertyEditorRegistrar
#Bean
public static CustomEditorConfigurer customEditorConfigurer(){
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setPropertyEditorRegistrars(
new PropertyEditorRegistrar[]{new ServerAddressPropertyEditorRegistrar()});
return configurer;
}
#Override
protected String getDatabaseName() {
return authenticationDb;
}
#Override
#Bean
public MongoClient mongoClient() {
MongoClient mongoClient = new MongoClient(Arrays.asList(new ServerAddress(host, port)), mongoCredentials(), mongoClientOptions());
return mongoClient;
}
#Bean
public MongoClientOptions mongoClientOptions() {
MongoClientOptions.Builder mongoClientOptions = MongoClientOptions.builder().sslInvalidHostNameAllowed(true).sslEnabled(true);
try {
InputStream inputStream = new ByteArrayInputStream(certificateDecoded.getBytes(StandardCharsets.UTF_8));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) certificateFactory.generateCertificate(inputStream);
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null); // You don't need the KeyStore instance to come from a file.
keyStore.setCertificateEntry("caCert", caCert);
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
mongoClientOptions.sslContext(sslContext);
mongoClientOptions.sslInvalidHostNameAllowed(true);
} catch (Exception e) {
throw new IllegalStateException(e);
}
return mongoClientOptions.build();
}
private MongoCredential mongoCredentials() {
return MongoCredential.createCredential(username, authenticationDb, password);
}
//With MongoTemplate you have access to db.
#Bean
public MongoTemplate mongoTemplate() {
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(mongoClient(), defaultDatabase);
return new MongoClient(factory);
}
}
public final class ServerAddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
#Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(ServerAddress[].class, new ServerAddressPropertyEditor());
}
}
Spring Boot 2.3.4 and Reactive Mongo in Eclipse:
pfx goes into src/test/resources
#Bean #Profile("dev")
public MongoClientSettings mongoClientSettingsDev() throws NoSuchAlgorithmException {
System.setProperty ("javax.net.ssl.keyStore","target/test-classes/xxx.pfx");
System.setProperty ("javax.net.ssl.keyStorePassword","xxx");
SSLContext sslContext = SSLContext.getDefault();
MongoClientSettings settings = MongoClientSettings.builder()
.applyToSslSettings(builder -> {
builder.enabled(true);
builder.context(sslContext);
})
.build();
return settings;
}
bootstrap.yml for connection with x.509:
spring:
data:
mongodb:
database: database_name
uri: mongodb://CN=xxx.xxx.com#cloud.xxx.com:62017/?authMechanism=MONGODB-X509&tls=true&authSource=$external
Update for Spring Boot 2.4.5
#Bean #Profile("dev")
public MongoClientSettings mongoClientSettingsDev(
MongoProperties properties,
Environment environment
) throws NoSuchAlgorithmException {
System.setProperty ("javax.net.ssl.keyStore","target/test-classes/xxx.pfx");
System.setProperty ("javax.net.ssl.keyStorePassword","xxx");
SSLContext sslContext = SSLContext.getDefault();
MongoClientSettings.Builder builder = MongoClientSettings.builder();
builder.applyToSslSettings(b -> {
b.enabled(true);
b.context(sslContext);
});
new MongoPropertiesClientSettingsBuilderCustomizer(properties, environment).customize(builder);
return builder.build();
}
Another way of setting the SSLContext:
Resource resource = new ClassPathResource("xxx.pfx");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(resource.getInputStream(), "xxx".toCharArray());
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(ks, "xxx".toCharArray()).build();
It is explained in the docs : please refer below :
http://mongodb.github.io/mongo-java-driver/3.0/driver/reference/connecting/ssl/?_ga=1.122423051.1001600813.1475930911
Also following configuration can be used to enable it
#Bean
public MongoClientOptions mongoClientOptions(){
System.setProperty ("javax.net.ssl.keyStore","<<PATH TO KEYSTOR >>");
System.setProperty ("javax.net.ssl.keyStorePassword","PASSWORD");
MongoClientOptions.Builder builder = MongoClientOptions.builder();
MongoClientOptions options=builder.sslEnabled(true).build();
return options;
}
pass the mongo client options to MongoClient instance as an argument
public MongoClient(ServerAddress addr, MongoClientOptions options) {
super(addr, options);
}
Adding further, when mongo processs is started with
mongo
--ssl --sslAllowInvalidCertificates --host --port
clients connecting to the mongo process dont have to set any options to support this.
You can also build the ssl enabled mongo instance in the following way.
public #Bean MongoClient mongoClient() throws Exception {
return new MongoClient(new MongoClientURI("mongodb://username:password#host:port/db?ssl=true"));
}
If you using spring boot then it can be configurable in application.properties or application.yml in the following way
spring.data.mongodb.uri=mongodb://username:password#host:port/db?ssl=true
Create a Bean as below and use where ever needed.
#Configuration
#ComponentScan(basePackages = "XXXXX")
#EnableMongoRepositories({ "XXXXXX" })
public class ApplicationConfig {
System.setProperty ("javax.net.ssl.keyStore", "<CERT>.keystore");
System.setProperty ("javax.net.ssl.keyStorePassword","<password>");
MongoCredential credential = MongoCredential.createMongoX509Credential(
"C=US,ST=XXXXXXX,O=XXXXXX... "
);
MongoClientSettings.Builder settings = MongoClientSettings.builder();
settings.credential(credential);
SSLContext sslContext = SSLContext.getDefault();
MongoClientSettings settings = MongoClientSettings.builder()
.applyToSslSettings(builder -> {
builder.enabled(true);
builder.context(sslContext);}).credential(credential).applyConnectionString(new ConnectionString("mongodb://<host>:<port>/?authMechanism=MONGODB-X509&ssl=true"))
.build();
com.mongodb.client.MongoClient client = MongoClients.create(settings);
SimpleMongoClientDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, <database>);
MongoTemplate mongoTemplate = new MongoTemplate(factory);
return mongoTemplate;
}
}

Resources