How to consume basic-authentication protected Restful web service via REACTIVE feign client - spring-boot

#ReactiveFeignClient(name = "service.b",configuration = CustomConfiguration.class)
public interface FeingConfiguration {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Help, how can I add a basic authentication to my header that I have in the service: service.b.
I have the CustomConfiguration.class class but it doesn't allow me, I have 401 authorization failed
#Configuration
public class CustomConfiguration {
#Bean
public BasicAuthRequestInterceptor basic() {
return new BasicAuthRequestInterceptor("user","user") ;
}

Looks like you are trying to use feign-reactive (https://github.com/Playtika/feign-reactive) to implement your REST clients. I am also using it for one of my projects and it looks like this library does not have an out-of-the-box way to specify basic auth credentials. At least no way to do this declaratively. So I didn't find a better way to do this than to abandon the auto-configuration via #ReactiveFeignClient and start configuring reactive feign clients manually. This way you can manually add "Authorization" header to all outgoing requests. So, provided this client definition:
public interface FeingClient {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Add the following configuration class to your Spring context, replacing username, password and service-url with your own data:
#Configuration
public class FeignClientConfiguration {
#Bean
FeignClient feignClient() {
WebReactiveFeign
.<FeignClient>builder()
.addRequestInterceptor(request -> {
request.headers().put(
"Authorization",
Collections.singletonList(
"Basic " + Base64.getEncoder().encodeToString(
"username:password".getBytes(StandardCharsets.ISO_8859_1))));
return request;
})
.target(FeignClient.class, "service-url");
}
}
Note, that this API for manual configurftion of reactive feign clients can differ between different versions of the reactive-feign library. Also note that this approach has a major drawback - if you start creating beans for your feign clients manually you lose the main advantage of Feign - ability to write REST-clients declaratively with just a few lines of code. E.g. if you want to use the above client with some sort of client-side load-balancing mechanism, like Ribbon/Eureka or Ribbon/Kubernetes, you will also need to configure that manually.

You can use a direct interceptor:
#Configuration
class FeignClientConfiguration {
#Bean
fun reactiveHttpRequestInterceptor(): ReactiveHttpRequestInterceptor {
return ReactiveHttpRequestInterceptor { request: ReactiveHttpRequest ->
request.headers()["Authorization"] = //insert data from SecurityContextHolder;
Mono.just(request)
}
}
}

Related

How to disable Mutual Authentication for a certain URL

I a project I am working on, we configured our current Jetty Server with the following configs:
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(tlsCfg.tlsKeystoreLocation);
sslContextFactory.setKeyStorePassword(tlsCfg.tlsKeystorePassword);
sslContextFactory.setNeedClientAuth(tlsCfg.useMutualAuth);
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.3");
// cipher suites that are not secure are properly removed by default constructor.
if (tlsCfg.useMutualAuth) {
sslContextFactory.setTrustStorePath(tlsCfg.tlsTruststoreLocation);
sslContextFactory.setTrustStorePassword(tlsCfg.tlsTruststorePassword);
}
JettyHttpContainerFactory.createServer(getSecureBaseURI(port), sslContextFactory, rc);
Our application has status resource which looks like the following:
#Path("status")
public class StatusResource {
#Autowired(required = false) // optional dependencies are not supported by #Inject
public StatusResource(Supplier<List<Status>> subStatusesSupplier) {
this.subStatusesSupplier = subStatusesSupplier;
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Status status() {
...
return status;
}
}
For our Operations Team it would be very helpful if this Endpoint (and only this endpoint) would not require the MutalAuthentication and the Tls-Settings.
Is there an option to exclude one endpoint from the whole SslContextFactory?
Not possible.
TLS/SSL occurs way before the request is even made.
The request contains the URI to the endpoint that you want to limit the mutual authentication on.
The only way you can accomplish this is with 2 ServerConnectors, each with their own SslContextFactory, one with mutual auth, one without.

Spring Cloud - HystrixCommand - How to properly enable with shared libraries

Using Springboot 1.5.x, Spring Cloud, and JAX-RS:
I could use a second pair of eyes since it is not clear to me whether the Spring configured, Javanica HystrixCommand works for all use cases or whether I may have an error in my code. Below is an approximation of what I'm doing, the code below will not actually compile.
From below WebService lives in a library with separate package path to the main application(s). Meanwhile MyWebService lives in the application that is in the same context path as the Springboot application. Also MyWebService is functional, no issues there. This just has to do with the visibility of HystrixCommand annotation in regards to Springboot based configuration.
At runtime, what I notice is that when a code like the one below runs, I do see "commandKey=A" in my response. This one I did not quite expect since it's still running while the data is obtained. And since we log the HystrixRequestLog, I also see this command key in my logs.
But all the other Command keys are not visible at all, regardless of where I place them in the file. If I remove CommandKey-A then no commands are visible whatsoever.
Thoughts?
// Example WebService that we use as a shared component for performing a backend call that is the same across different resources
#RequiredArgsConstructor
#Accessors(fluent = true)
#Setter
public abstract class WebService {
private final #Nonnull Supplier<X> backendFactory;
#Setter(AccessLevel.PACKAGE)
private #Nonnull Supplier<BackendComponent> backendComponentSupplier = () -> new BackendComponent();
#GET
#Produces("application/json")
#HystrixCommand(commandKey="A")
public Response mainCall() {
Object obj = new Object();
try {
otherCommandMethod();
} catch (Exception commandException) {
// do nothing (for this example)
}
// get the hystrix request information so that we can determine what was executed
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = hystrixExecutedCommands();
// set the hystrix data, viewable in the response
obj.setData("hystrix", executedCommands.orElse(Collections.emptyList()));
if(hasError(obj)) {
return Response.serverError()
.entity(obj)
.build();
}
return Response.ok()
.entity(healthObject)
.build();
}
#HystrixCommand(commandKey="B")
private void otherCommandMethod() {
backendComponentSupplier
.get()
.observe()
.toBlocking()
.subscribe();
}
Optional<Collection<HystrixInvokableInfo<?>>> hystrixExecutedCommands() {
Optional<HystrixRequestLog> hystrixRequest = Optional
.ofNullable(HystrixRequestLog.getCurrentRequest());
// get the hystrix executed commands
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = Optional.empty();
if (hystrixRequest.isPresent()) {
executedCommands = Optional.of(hystrixRequest.get()
.getAllExecutedCommands());
}
return executedCommands;
}
#Setter
#RequiredArgsConstructor
public class BackendComponent implements ObservableCommand<Void> {
#Override
#HystrixCommand(commandKey="Y")
public Observable<Void> observe() {
// make some backend call
return backendFactory.get()
.observe();
}
}
}
// then later this component gets configured in the specific applications with sample configuraiton that looks like this:
#SuppressWarnings({ "unchecked", "rawtypes" })
#Path("resource/somepath")
#Component
public class MyWebService extends WebService {
#Inject
public MyWebService(Supplier<X> backendSupplier) {
super((Supplier)backendSupplier);
}
}
There is an issue with mainCall() calling otherCommandMethod(). Methods with #HystrixCommand can not be called from within the same class.
As discussed in the answers to this question this is a limitation of Spring's AOP.

Feign with RibbonClient and Consul discovery without Spring Cloud

I was trying to setup Feign to work with RibbonClient, something like MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");, where myAppProd is an application which I can see in Consul. Now, if I use Spring annotations for the Feign client (#FeignClient("myAppProd"), #RequestMapping), everything works as Spring Cloud module will take care of everything.
If I want to use Feign.builder() and #RequestLine, I get the error:
com.netflix.client.ClientException: Load balancer does not have available server for client: myAppProd.
My first initial thought was that Feign was built to work with Eureka and only Spring Cloud makes the integration with Consul, but I am unsure about this.
So, is there a way to make Feign work with Consul without Spring Cloud?
Thanks in advance.
In my opinion, it's not feign work with consul, its feign -> ribbon -> consul.
RibbonClient needs to find myAppProd's serverList from its LoadBalancer.
Without ServerList, error: 'does not have available server for client'.
This job has been done by SpringCloudConsul and SpringCloudRibbon project, of course you can write another adaptor, it's just some glue code. IMHO, you can import this spring dependency into your project, but use it in non-spring way . Demo code:
just write a new feign.ribbon.LBClientFactory, that generate LBClient with ConsulServerList(Spring's class).
public class ConsulLBFactory implements LBClientFactory {
private ConsulClient client;
private ConsulDiscoveryProperties properties;
public ConsulLBFactory(ConsulClient client, ConsulDiscoveryProperties consulDiscoveryProperties) {
this.client = client;
this.properties = consulDiscoveryProperties;
}
#Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ConsulServerList consulServerList = new ConsulServerList(this.client, properties);
consulServerList.initWithNiwsConfig(config);
ZoneAwareLoadBalancer<ConsulServer> lb = new ZoneAwareLoadBalancer<>(config);
lb.setServersList(consulServerList.getInitialListOfServers());
lb.setServerListImpl(consulServerList);
return LBClient.create(lb, config);
}
}
and then use it in feign:
public class Demo {
public static void main(String[] args) {
ConsulLBFactory consulLBFactory = new ConsulLBFactory(
new ConsulClient(),
new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties()))
);
RibbonClient ribbonClient = RibbonClient.builder()
.lbClientFactory(consulLBFactory)
.build();
GitHub github = Feign.builder()
.client(ribbonClient)
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
interface GitHub {
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
}
you can find this demo code here, add api.github.com to your local consul before running this demo.

How to define the execution order of interceptor in Spring Boot application?

I define an interceptor and register it in a class (annotated with Configuration) which extends WebMvcConfigurerAdapter; however, I also use some third-party libraries which also define some interceptors. I want my interceptor to be the last one in the interceptor execution chain. It seems there is no way to enforce this. How to define the execution order of interceptor in Spring Boot application?
If we've Multiple Interceptors, Instead of #Order Annotation we can do as below.
#EnableWebMvc
#Configuration
public class WebMVCConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addWebRequestInterceptor(new WebRequestInterceptor() {
//Overrides
}).order(Ordered.HIGHEST_PRECEDENCE);
registry
.addWebRequestInterceptor(new WebRequestInterceptor() {
//Overrides
}).order(Ordered.LOWEST_PRECEDENCE);
}
}
All Feign clients become proxies, so there is only one way to change the order of the request interceptors. But you cannot use it, because if you change the proxy to SGLIB, it will not work.
if(bean instanceof YoursFeignClientBean) {
Class<Proxy> superclass = (Class<Proxy>) bean.getClass().getSuperclass();
Field h = superclass.getDeclaredField("h");
h.setAccessible(true);
// its FeignInvocationHandler
InvocationHandler ih = (InvocationHandler) ReflectionUtils.getField(h, bean);
Field dispatch = ih.getClass().getDeclaredField("dispatch");
dispatch.setAccessible(true);
Map<Method, InvocationHandlerFactory.MethodHandler> map =
(Map<Method, InvocationHandlerFactory.MethodHandler>) dispatch.get(ih);
for (Method method : map.keySet()) {
InvocationHandlerFactory.MethodHandler handler = map.get(method);
Field requestInterceptors = handler.getClass().getDeclaredField("requestInterceptors");
requestInterceptors.setAccessible(true);
List<RequestInterceptor> interceptorList = (List<RequestInterceptor>)
ReflectionUtils.getField(requestInterceptors, handler);
RequestInterceptor ri = interceptorList.stream().filter(t -> t.getClass().getName().startsWith(YoursFeignConfig.class.getName())).findFirst().get();
// reorder
interceptorList.remove(ri);
interceptorList.add(ri);
}
According to my experience, Interceptors are adding a stack. For this reason you should addRegister above which one you want before calling than other.

How can I send a message on connect event (SockJS, STOMP, Spring)?

I am connection through SockJS over STOMP to my Spring backend. Everything work fine, the configuration works well for all browsers etc. However, I cannot find a way to send an initial message. The scenario would be as follows:
The client connects to the topic
function connect() {
var socket = new SockJS('http://localhost:8080/myEndpoint');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/notify', function(message){
showMessage(JSON.parse(message.body).content);
});
});
}
and the backend config looks more or less like this:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketAppConfig extends AbstractWebSocketMessageBrokerConfigurer {
...
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/myEndpoint").withSockJS();
}
I want to send to the client an automatic reply from the backend (on the connection event) so that I can already provide him with some dataset (e.g. read sth from the db) without the need for him (the client) to send a GET request (or any other). So to sum up, I just want to send him a message on the topic with the SimMessagingTemplate object just after he connected.
Usually I do it the following way, e.g. in a REST controller, when the template is already autowired:
#Autowired
private SimpMessagingTemplate template;
...
template.convertAndSend(TOPIC, new Message("it works!"));
How to achieve this on connect event?
UPDATE
I have managed to make it work. However, I am still a bit confused with the configuration. I will show here 2 configurations how the initial message can be sent:
1) First solution
JS part
stompClient.subscribe('/app/pending', function(message){
showMessage(JSON.parse(message.body).content);
});
stompClient.subscribe('/topic/incoming', function(message){
showMessage(JSON.parse(message.body).content);
});
Java part
#Controller
public class WebSocketBusController {
#SubscribeMapping("/pending")
Configuration
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
... and other calls
template.convertAndSend("/topic/incoming", outgoingMessage);
2) Second solution
JS part
stompClient.subscribe('/topic/incoming', function(message){
showMessage(JSON.parse(message.body).content);
})
Java part
#Controller
public class WebSocketBusController {
#SubscribeMapping("/topic/incoming")
Configuration
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
// NO APPLICATION PREFIX HERE
}
... and other calls
template.convertAndSend("/topic/incoming", outgoingMessage);
SUMMARY:
The first case uses two subscriptions - this I wanted to avoid and thought this can be managed with one only.
The second one however has no prefix for application. But at least I can have a single subscription to listen on the provided topic as well as send initial message.
If you just want to send a message to the client upon connection, use an appropriate ApplicationListener:
#Component
public class StompConnectedEvent implements ApplicationListener<SessionConnectedEvent> {
private static final Logger log = Logger.getLogger(StompConnectedEvent.class);
#Autowired
private Controller controller;
#Override
public void onApplicationEvent(SessionConnectedEvent event) {
log.debug("Client connected.");
// you can use a controller to send your msg here
}
}
You can't do that on connect, however the #SubscribeMapping does the stuff in that case.
You just need to mark the service method with that annotation and it returns a result to the subscribe function.
From Spring Reference Manual:
An #SubscribeMapping annotation can also be used to map subscription requests to #Controller methods. It is supported on the method level, but can also be combined with a type level #MessageMapping annotation that expresses shared mappings across all message handling methods within the same controller.
By default the return value from an #SubscribeMapping method is sent as a message directly back to the connected client and does not pass through the broker. This is useful for implementing request-reply message interactions; for example, to fetch application data when the application UI is being initialized. Or alternatively an #SubscribeMapping method can be annotated with #SendTo in which case the resulting message is sent to the "brokerChannel" using the specified target destination.
UPDATE
Referring to this example: https://github.com/revelfire/spring4Test how would that be possible to send anything when the line 24 of the index.html is invoked: stompClient.subscribe('/user/queue/socket/responses' ... from the spring controllers?
Well, look like this:
#SubscribeMapping("/queue/socket/responses")
public List<Employee> list() {
return getEmployees();
}
The Stomp client part remains the same.

Resources