How to set a custom Feign RequestInterceptor for specific clients? - spring-boot

I need to add custom Authorization header to some new feign clients. So I write an RequestInterceptor and it worked but the point is I don't want this custom RequestInterceptor affect my old clients. I tried to filter using template.url() method but it doesn't give me the entire url of the request and it only contains the client method url (not url and path which is announced above the client class).
My Question is that how can I target the interceptor?
This is my configuration:
#Configuration
open class FeignCustomConfiguration {
private fun generateToken(): String { ... }
#Bean
open fun requestInterceptor(): RequestInterceptor {
return RequestInterceptor {
it.header("Authorization", generateToken())
}
}
}

I found the solution.
For each FeignClient there is a configuration option which accepts an array of classes. The syntax of assigning a class to configuration in kotlin is as follow:
#FeignClient(
name = "feign-client",
path = "/path",
url = "https://example.com",
configuration = [FeignCustomConfiguration::class]
)
interface FeignCustomClient {
...
}
With this assignment, each FeignClient has its own configuration and RequestInterceptor doesn't deal with other clients.

Related

Is there support for multiple feign.Client beans within Spring

Some context
The project is a spring boot application (v 2.3.12) using spring-cloud-openfeign-core (v 2.2.8) to make REST requests.
The service has 2 clients
one that needs to make REST requests using a proxy
one that needs to make REST request without a proxy
I'm unable to get both client to work simultaneously, i.e. have requests work for both proxied and non-proxied resources.
Is it possible to use different configuration files to support this, I have tried something like this
Configuration class X
#bean
public Client client1() {
return new Client.Default(null, null);
}
Configuration class Y
#bean
public Client client2() {
return new Client.Proxied(null, null,
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.0.0.1", 8080)));
}
Feign interface code looks something like this
#Service
#FeignClient(name = "service1", url = "internal-url", configuration = X.class)
#Headers("Content-Type: application/json")
public interface serviceClient1 {
#PostMapping(value = "/v1/path}")
Response1 update(#RequestBody Request1 request1);
}
#Service
#FeignClient(name = "service2", url = "external-url", configuration = Y.class)
#Headers("Content-Type: application/json")
public interface serviceClient2 {
#PostMapping(value = "/v1/path}")
Response2 update(#RequestBody Request2 request2);
}
It seems only one of the client beans is used when making requests.

Duplicate config code problem Spring Boot microservices

Is there a way to create a spring configuration class and use it to all my microservices?
For example, I have to copy the following configuration class and paste it through all my microservices which means that when I want to make a small change I have to go through all the microservices editing the same class.
I have investigated and the most I could find is to create a module with all the classes in common and import it in my microservices by the pom, what happens with this is that when I want to get the SecurityContextHolder.getContext() this is null for a context issue and I do not know very well how to give solution to this or what other alternatives there are.
#Configuration
public class FeignGlobalConfiguration {
#Bean
public ErrorDecoder errorDecoder() {
return new RetrieveMessageErrorDecoder();
}
#Bean
public RequestInterceptor requestInterceptor(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return requestTemplate -> {
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}
}
The problem is your bean definition.
The line Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); is called when the bean is constructed so only once. After that the reference is used (which is probably null at the time of construction.
To solve move tha tline inside the lambda so it gets evaluated each time a request is processed.
#Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}

Spring cloud stream messaging system(RabbitMQ) implementation using Rest Controller(Restful API)

From past few days i'm trying to implement the Spring cloud stream messaging system using RestController, but it is not happening through the current implementation.
For this sample code i'm going to add RestController
#EnableBinding(Source.class)
#EnableConfigurationProperties(TimeSourceOptionsMetadata.class)
public class TimeSource {
#Autowired
private TimeSourceOptionsMetadata options;
#InboundChannelAdapter(value = Source.OUTPUT)
public String timerMessageSource() {
return new SimpleDateFormat(this.options.getFormat()).format(new Date());
}
}
But the #InboundChannelAdapter cannot accept any parameters from RequestMapping Get Method URL.At the end what i need is to add message to the broker using Restful API Get method from api call. which is the best way to do it?, I couldn't figure out any best process from internet.
spring cloud team already provided a source application that listens for HTTP requests and emits the body as a message payload. If the Content-Type matches text/* or application/json, the payload will be a String, otherwise the payload will be a byte array.
github link
You can go with this or if you want to write it yourself, you can do it like below:
#RestController
#EnableBinding(Source.class)
public class RestSource {
#Autowired
private Source channels;
#RequestMapping(path = "/", method = POST, consumes = {"application/json" })
#ResponseStatus(HttpStatus.ACCEPTED)
public void handleRequest(#RequestBody String body, #RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) {
sendMessage(body, contentType);
}
private void sendMessage(Object body, Object contentType) {
channels.output().send(MessageBuilder.createMessage(body,
new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType))));
}
}

Response MIME type for Spring Boot actuator endpoints

I have updated a Spring Boot application from 1.4.x to 1.5.1 and the Spring Actuator endpoints return a different MIME type now:
For example, /health is now application/vnd.spring-boot.actuator.v1+json instead simply application/json.
How can I change this back?
The endpoints return a content type that honours what the client's request says it can accept. You will get an application/json response if the client send an Accept header that asks for it:
Accept: application/json
In response to the comment of https://stackoverflow.com/users/2952093/kap (my reputation is to low to create a comment): when using Firefox to check endpoints that return JSON I use the Add-on JSONView. In the settings there is an option to specify alternate JSON content types, just add application/vnd.spring-boot.actuator.v1+jsonand you'll see the returned JSON in pretty print inside your browser.
As you noticed the content type for actuators have changed in 1.5.x.
If you in put "application/json" in the "Accept:" header you should get the usual content-type.
But if you don't have any way of modifying the clients, this snippet returns health (without details) and original content-type (the 1.4.x way).
#RestController
#RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#Inject
HealthEndpoint healthEndpoint;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Health > health() throws IOException {
Health health = healthEndpoint.health();
Health nonSensitiveHealthResult = Health.status(health.getStatus()).build();
if (health.getStatus().equals(Status.UP)) {
return ResponseEntity.status(HttpStatus.OK).body(nonSensitiveHealthResult);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(nonSensitiveHealthResult);
}
}
}
Configuration (move away existing health)
endpoints.health.path: internal/health
Based on the code in https://github.com/spring-projects/spring-boot/issues/2449 (which also works fine but completely removes the new type) I came up with
#Component
public class ActuatorCustomizer implements EndpointHandlerMappingCustomizer {
static class Fix extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Object attribute = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (attribute instanceof LinkedHashSet) {
#SuppressWarnings("unchecked")
LinkedHashSet<MediaType> lhs = (LinkedHashSet<MediaType>) attribute;
if (lhs.remove(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON)) {
lhs.add(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON);
}
}
return true;
}
}
#Override
public void customize(EndpointHandlerMapping mapping) {
mapping.setInterceptors(new Object[] {new Fix()});
}
}
which puts the new vendor-mediatype last so that it will use application/json for all actuator endpoints when nothing is specified.
Tested with spring-boot 1.5.3
Since SpringBoot 2.0.x the suggested solution in implementing the EndpointHandlerMappingCustomizer doesn't work any longer.
The good news is, the solution is simpler now.
The Bean EndpointMediaTypes needs to be provided. It is provided by the SpringBoot class WebEndpointAutoConfiguration by default.
Providing your own could look like this:
#Configuration
public class ActuatorEndpointConfig {
private static final List<String> MEDIA_TYPES = Arrays
.asList("application/json", ActuatorMediaType.V2_JSON);
#Bean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
}
To support application/vnd.spring-boot.actuator.v1+json in Firefox's built in JSON viewer, you can install this addon: json-content-type-override. It will convert content types that contain "json" to "application/json".
Update: Firefox 58+ has built-in support for these mime types, and no addon is needed anymore. See https://bugzilla.mozilla.org/show_bug.cgi?id=1388335

Spring Cloud : Using routing type filter in Zuul

I have 2 micro-services (Service A and Service B) built using Spring Boot, which gets routed through a Zuul Proxy also built as a Spring Boot app and I have checked that the Zuul proxy works just fine. However, what I am trying to do is to write a custom routing type ZuulFilter which should first route to Service A when a request comes in for Service B. Here is what I need assistance for:
I would like to know an example of how a routing filter looks like as I do not see anything after searching the internet. What I get are some examples of pre-filter and Netflix's documentation doesn't help much as well on that aspect.
Whether writing a custom route filter would mess up the original routing behavior of Zuul
I would construct a Feign client in the Zuul filter and make the call to service A using it. Feign will populate a ribbon load balancer to make the call in just the same way that Zuul does when proxying.
I had the same issue and this is what I came up with.
public class ServicesLegacyRouteFilter extends ZuulFilter {
private ServiceB serviceB;
public ServiceLegacyRouteFilter(ServiceB serviceB) {
this.serviceB = serviceB;
}
#Override
public String filterType() {
return ROUTE_TYPE;
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if ("serviceA".equals(ctx.get("serviceId"))) {
//call Service B here and use return type to set
//the final destination service
String destination = serviceB.routeWhere();
ctx.set("serviceId", destination);
return true;
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// Or call ServiceB here to make your determination on
// the final destination.
String destination = serviceB.routeWhere();
ctx.set("serviceId", destination);
return null;
}
}
My actual production use case was more complicated on the routing of course, but this is the basics of how I was able to change routes based on what was coming in and how to take advantage of Zuul to get it out to the correct service.

Resources