How do I configure reactor-netty to use SSL? - spring

I am trying to get familiar with Spring's Project Reactor (https://projectreactor.io/) and have built a small application to make REST calls to another service over SSL. I cannot find any way to configure the org.springframework.web.client.reactive.WebClient to make requests over SSL. There seems to be no documentation about this. I am using reactor-core 3.0.0.RC1 and reactor-netty 0.5.0.M3, and Spring Framework 5.0.0.M1. Does anyone know how to configure reactor-netty with SSL support?

Update 2017-01-04:
This was corrected in the 5.0.0.M4 release of Spring Framework with this patch.
Original Answer:
I discovered that the solution is to create a new ClientHttpConnector implementation that respects SSL.
public class ReactorClientHttpsAwareConnector implements ClientHttpConnector {
#Override
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
return reactor.ipc.netty.http.HttpClient.create()
.request(io.netty.handler.codec.http.HttpMethod.valueOf(method.name()),
uri.toString(),
httpClientRequest -> requestCallback
.apply(new ReactorClientHttpRequest(method, uri, httpClientRequest)))
.cast(HttpInbound.class)
.otherwise(HttpException.class, exc -> Mono.just(exc.getChannel()))
.map(ReactorClientHttpResponse::new);
}
}
HttpClient.create() is required to make the client SSL-aware.

Related

Override URL and Authentication configuration of Spring Cloud Feign client

I am trying to leverage Spring Cloud Feign client for declaring rest endpoints(third-party-endpoints) which will be called based on the request that my controller receives.
I have declared a feign client interface like:
#FeignClient(name = "my-client", url = "https://abc.xyz.com", configuration = MyClientConfiguration.class)
public interface MyFeignClient{
}
MyClientConfiguration is an unannotated feign configuration class just exposing BasicAuthRequestInterceptor bean preconfigured with some credential.
public class MyClientConfiguration {
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(){
return new BasicAuthRequestInterceptor("USERID","PWD");
}
}
So, what I have right now is a preconfigured feign client.
I am not sure how do I deal with the requirement that asks for being able to define/override the feign configuration with the url and credentials received in the body of different POST request.
So its not about overriding the feign configuration via properties file(like other somewhat similar SO questions), but instead being able to configure the feign client on the fly based on the url and credentials received in the body of the incoming requests.
I am aware of ways to do it with WebClient and looking for something similar to what WebClient provides via its uri and headers methods. Webclient allows us to override the webClient bean configuration with different uri and different auth credentials,
webClient
.put()
.uri("base-uri", uriBuilder -> uriBuilder.path("/rest").path("/api").path("/2").path("/issue").path("/" + id).build())
.headers(httpHeaders -> {
httpHeaders.setBasicAuth("decrypted-username", "decrypted-pwd");
})
Does Spring Cloud Feign provide any such facility where it is possible to have a generic configuration yet allowing to override those configurations at runtime?
EDIT 1 : START
The closest I got to is implementing via the feign's builder pattern :
MyFeignClient myFeignClient = Feign.builder()
.contract(new SpringMvcContract())
.requestInterceptor(new BasicAuthRequestInterceptor("decrypted-username", "decrypted-pwd")
.target(MyFeignClient.class, myRequestDTO.getBaseUrl());
I was challenged with an exception because I didn't specify which Contract to use. It was using the Default Contract.I then changed to SpringMvcContract as the annotation that I am using are the Spring MVC ones and not feign's default.
Getting DecodeException as of now. Trying to fix it. Will post update when done.
EDIT 1 : END
EDIT 2 : START
I was finally able to get this working. I figured out that the feign-gson does a great job at deserialization with minimal configuration.
My final feign configuration with the Decoder in place looks like :
MyFeignClient myFeignClient = Feign.builder()
.contract(new SpringMvcContract())
.decoder(new GsonDecoder())
.requestInterceptor(new BasicAuthRequestInterceptor("decrypted-username", "decrypted-pwd")
.target(MyFeignClient.class, myRequestDTO.getBaseUrl());
EDIT 2 : END

Reactive rest client headers injection in Quarkus

I am following the guide of the new quarkus-resteasy-reactive-jackson extension to use it in an existing Quarkus application deployed in production.
In the Custom headers support section it's introduced the ClientHeadersFactory interface to allow injecting headers in a request, but you are forced to return a sync response. One can not use Uni<MultivaluedMap<String, String>>, which is of what is desired in my case, because I need to add a token in the header, and this token is retrieved by a request to another rest endpoint that returns a Uni<Token>.
How can I achieve this in the new implementation? If not possible, is there a workaround?
It's not possible to use Uni<MultivaluedMap<...>> in ClientHeadersFactory in Quarkus 2.2.x (and older versions). We may add such a feature in the near future.
Currently, you can #HeaderParam directly. Your code could probably look as follows:
Uni<String> token = tokenService.getToken();
token.onItem().transformToUni(tokenValue -> client.doTheCall(tokenValue));
Where the client interface would be something like:
#Path("/")
public interface MyClient {
#GET
Uni<Foo> doTheCall(#HeaderParam("token") String tokenValue);
}

Created RestHighLevelClient for Elasticsearch won't use SSL

I am trying to connect to an existing Elasticsearch instance. The instance is set up and working fine, which I can confirm by accessing it through the browser under https://localhost:9200. I am using Spring Data Elasticsearch version 4.1.2 and I am following the official instructions from https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.rest.
Unfortunately, when starting the application, the initial health checks of the elasticsearch instance fail, because the code is trying to use http instead of https:
o.s.w.r.f.client.ExchangeFunctions : [377e90b0] HTTP HEAD http://localhost:9200/
Therefore I added the ".usingSsl()" to the ClientConfiguration, but it does not seem to have any effect and I just don't understand why. The "funny" thing is, if I implement the reactive client as described here (https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.reactive), https suddenly works and the applications starts perfectly fine. However, I want to use the regular high level client.
Can you help me change the configuration in a way so that it finally makes use of https instead of http?
#Configuration
class ElasticsearchClientConfig: AbstractElasticsearchConfiguration() {
#Bean
override fun elasticsearchClient(): RestHighLevelClient {
val clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.usingSsl()
.build();
return RestClients.create(clientConfiguration).rest();
}
}

How to specificy preconfigured webshpere-liberty SSLSocketFactory to Netty rective client used by spring-webclient

I am building a spring boot application that runs on #websphere-liberty profile. I am using a Spring webclient for outbound connections. All good except that I am not able to specify liberty SSL context to the netty httpclient used by Spring Webclient.
Any help on how how to specify pre-configured libery ssl context to Netty httpclient is greatly appreciated.
IBM documentation says that I should use SSLSocketFactory.getDefault() to get liberty SSL context. But I don't see any way to pass this to Netty HttpClient.
Ref: [https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_sec_ssl.html]
This is what I tried, but it is not pointing to the SSL context configured by #Websphere-liberty. Instead, it seems to create a new one. Because of that, the trust/keystores are not available to the netty HttpClient and I get an error saying that the peer is not trusted.
#Bean
public WebClient createWebClient() throws SSLException {
JdkSslContext jdkSslContext = new
JdkSslContext(SSLContext.getInstance("SSL"),true,
null, IdentityCipherSuiteFilter.INSTANCE,null,ClientAuth.NONE,
null,false);
ClientHttpConnector httpConnector = HttpClient.create().secure { t -> t.sslContext(jdkSslContext) }
return WebClient.builder().clientConnector(httpConnector).build();
}

forcing the Jersey Grizzly server to authenicate

I am performing acceptance tests against REST services written using Jersey. In my services, I need to get the name of the user who successfully authenticated (BASIC) using:
#Context private SecurityContext securityContext; //at class level
String username = securityContext.getUserPrincipal().getName() //inside a resource
I am using the Jersey client API and running against the Grizzly server. I set up the server as follows:
public class BaseResourceTest extends JerseyTest {
public BaseResourceTest() throws Exception {
super(new WebAppDescriptor.Builder("net.tds.adm.na.batchcut")
.contextPath(baseUri.getPath())
.contextParam("contextConfigLocation","classpath:testContext.xml")
.initParam("com.sun.jersey.api.json.POJOMappingFeature",
"true").servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.build());
}
.......
}
In my tests, I pass an authorization header as follows:
WebResource resource = resource().path(_url);
return resource.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE).header(HttpHeaders.AUTHORIZATION, new String(Base64.encode("judge:smails"))).post(ClientResponse.class, _formData);
However, in my resources I get an NPE when trying to get the user name. I think I need to get the server to actively authenticate against a realm but I haven't found how to configure the server to do this. Has anybody had any experience with this?
Unfortunately Grizzly HttpServer doesn't support authentication at the moment.
But here you can find a simple example how to implement authentication using Jersey Filter.
http://simplapi.wordpress.com/2013/01/24/jersey-jax-rs-implements-a-http-basic-auth-decoder/

Resources