Spring Cloud Feign Interceptor - java

I have created a ClientHttpRequestInterceptor that I use to intercept all outgoing RestTemplate requests and responses. I would like to add the interceptor to all outgoing Feign requests/responses. Is there a way to do this?
I know that there is a feign.RequestInterceptor but with this I can only intercept the request and not the response.
There is a class FeignConfiguration that I found in Github that has the ability to add interceptors but I don't know in which maven dependency version it is.

A practical example of how to intercept the response in a Spring Cloud OpenFeign.
Create a custom Client by extending Client.Default as shown below:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
Response response = super.execute(request, options);
InputStream bodyStream = response.body().asInputStream();
String responseBody = StreamUtils.copyToString(bodyStream, StandardCharsets.UTF_8);
//TODO do whatever you want with the responseBody - parse and modify it
return response.toBuilder().body(responseBody, StandardCharsets.UTF_8).build();
}
}
Then use the custom Client in a configuration class:
public class FeignClientConfig {
public FeignClientConfig() { }
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}
Finally, use the configuration class in a FeignClient:
#FeignClient(name = "api-client", url = "${api.base-url}", configuration = FeignClientConfig.class)
public interface ApiClient {
}
Good luck

If you want to use feign from spring cloud, use org.springframework.cloud:spring-cloud-starter-feign as your dependency coordinates. Currently the only way to modify the response is to implement your own feign.Client.

Related

How to check HTTP request header for certain endpoints in Spring Framework

I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.

How to pass OAuth Client Credentials to a WebClient dynamically with Spring Security 5?

I'm developing a generic web client wrapper which should send requests to a OAuth2 protected resource. My idea was to implement a ClientWrapper which holds a WebClient. The ClientWrapper offers methods to access the resource like getFile(String registrationId). I thought I could register different ClientRegistration at runtime and obtain them with the WebClient.
So far I've registered a WebClient as follows:
ClienRegistration Bean:
#Bean
public ClientRegistration clientRegistration() {
ClientRegistration.Builder clientRegestrationBuilder = ClientRegistration
.withRegistrationId(CLIENT_REGISTRATION_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
return clientRegestrationBuilder.build();
}
In memory holder:
#Bean
public ReactiveClientRegistrationRepository clientRegistrations(List<ClientRegistration> clientRegistrations) {
return new InMemoryReactiveClientRegistrationRepository(clientRegistrations);
}
Web Client Builder
#Bean(OAUTH2_WEB_CLIENT)
public WebClient oauthFilteredWebClient(ReactiveClientRegistrationRepository clientRegistrationRepository) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}
I've injected it into my ClientWrapper:
public ClientWrapper(WebClient webClient) {
this.webClient = webClient;
}
#Override
public File getFile(String regId) {
Mono<File> result = webClient.get().uri("https://somepath") //
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction
.clientRegistrationId(regId))
.retrieve() //
.bodyToMono(File.class);
return result.block();
}
Doing it this way it is very static and I'm only able to use exactly one OAuth2 client (clientid/client-secret and token endpoint). With the idea providing a registration id it would be needed to register n ClientRegistration objects at runtime which I think is not possible and doesn't feel right... Is there a nice way to pass a "OAuthCredentials" object to a WebClient dynamically which is used for fetching a token?

Spring WebFlux block Http.CONNECT method

We have some security tests around our company in which the apps are tested in different ways. One of them is to try a CONNECT like:
telnet localhost 8080
CONNECT http://test.com HTTP/1.1
and in that case to return a 400 or 405. The existing Spring MVC apps return a 400, but it seems that our new Spring WebFlux:5.1.2.RELEASE app(Netty server) return a 200.
The first thing that I did was to shift to latest spring WebFlux version:5.1.4.RELEASE, and in this case the response http error code was:404, but was still not good enough. So I tried to:
Create a webFilter
Modify the CORS filter
Modify Spring Security chain
,but all these solutions failed. How'd you fix that? It would be a good idea to create a custom http handler ?
I've created a custom http handler:
public class AppContextPathCompositeHandler extends ContextPathCompositeHandler {
public AppContextPathCompositeHandler(Map<String, ? extends HttpHandler> handlerMap) {
super(handlerMap);
}
#Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
if (HttpMethod.CONNECT.name().equals(request.getMethodValue())) {
response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
return response.setComplete();
}
return super.handle(request, response);
}
}
and it was configured like:
#Configuration
public class NettyReactiveWebServerConfig extends NettyReactiveWebServerFactory {
#Value("${server.context-path}")
private String contextPath;
#Override
public WebServer getWebServer(HttpHandler httpHandler) {
Map<String, HttpHandler> handlerMap = new HashMap<>();
handlerMap.put(contextPath, httpHandler);
return super.getWebServer(new AppContextPathCompositeHandler(handlerMap));
}
}

How to implement "load balancer" using spring boot?

Depends on request body content I need to redirect http requests to URL_1 or URL_2.
I started controller implementation:
#RestController
public class RouteController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public HttpServletResponse route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
//send request to URL_1 and get response
} else {
//send request to URL_2 and get response
}
}
}
Request might be GET or POST ot PUT or PATCH etc.
Could you help me to write that code?
I've asked a somehow similar question a while ago. Plea see Server side redirect for REST call for more context.
The best way (to my current understanding) you could achieve this is by manually invoking the desired endpoints from your initial endpoint.
#RestController
public class RouteController {
#Value("${firstUrl}")
private String firstUrl;
#Value("${secondUrl}")
private String secondUrl;
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public void route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
restTemplate.exchange(firstUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params));
} else {
restTemplate.exchange(secondUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params))
}
}
}
Example implementation for getHttpMethod:
public HttpMethod getHttpMethod(HttpServletRequest request) {
return HttpMethod.valueOf(request.getMethod());
}
Similar implementations for getHttpEntity, getResponseClass and getParams. They are used for converting the data from the HttpServletRequest request to the types required by the exchange method.
There seem to be a lot of better ways of doing this for a Spring MVC app, but I guess that it does not apply to your context.
Another way you could achieve this would be defining your own REST client and adding the routing logic there.

Retrofit 2.0 multiple interceptors

I am working with retrofit and need to be able to use multiple interceptors. Currently I am using one to automatically append an auth token but i need to be able to make calls with no auth token. If i add another interceptor with no auth token in the header how do I use that one instead of the auth token interceptor.
val interceptor: Interceptor = Interceptor { chain ->
val newRequest = chain.request().newBuilder().
addHeader("Auth_Token", pref.getString(PSPreferences.prefAuthKey, "")).
cacheControl(CacheControl.FORCE_NETWORK).
build()
chain.proceed(newRequest)
}
okHttpClient = OkHttpClient.Builder().
readTimeout(1, TimeUnit.MINUTES).
connectTimeout(1, TimeUnit.MINUTES).
addInterceptor(interceptor).build()
val retrofitInstance = Retrofit.Builder()
.baseUrl(APIEndpointInterface.BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
apiInterface = retrofitInstance.create<APIEndpointInterface>(APIEndpointInterface::class.java)
OkHttpClient maintains a list of the interceptors which you can access, however it is an unmodifiable collection.
This leaves us with three options I believe:
Create two OkHttpClient instances, and by deduction two Retrofit
instances, one for the unauthenticated requests, and one for the
authenticated requests.
Check if you should use the interceptor, e.g. in your authentication interceptor, you can first check if there exists a key in your preferences for the token, and if so use it; if not, you simply proceed without modifying anything. You do this for your unauthenticated interceptor too. I think this is the easiest solution for your case.
Create a single interceptor, which will maintain a modifiable list
of interceptors which you can add and remove at will. You would need
to keep a reference to this interceptor, maybe make it a Singleton.
For the third option, I have provided a very simple example:
public class HttpRequestResponseInterceptor implements Interceptor {
public final List<RequestInterceptor> requestInterceptors = new ArrayList<>();
public final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
for (RequestInterceptor interceptor : requestInterceptors) {
request = interceptor.intercept(request);
}
Response response = chain.proceed(request);
for (ResponseInterceptor interceptor : responseInterceptors) {
response = interceptor.intercept(response);
}
return response;
}
public interface RequestInterceptor {
Request intercept(Request request) throws IOException;
}
public interface ResponseInterceptor {
Response intercept(Response response) throws IOException;
}
}
In this case you would need to implement the custom interfaces RequestInterceptor and ResponseInterceptor.
An example of what an implementation of these interfaces would look like:
public class ExampleInterceptor implements HttpRequestResponseInterceptor.RequestInterceptor,
HttpRequestResponseInterceptor.ResponseInterceptor {
#Override
public Request intercept(Request request) throws IOException {
return request.newBuilder().addHeader("REQUEST_HEADER", "EXAMPLE").build();
}
#Override
public Response intercept(Response response) throws IOException {
return response.newBuilder().addHeader("RESPONSE_HEADER", "EXAMPLE").build();
}
}
You would then need to add this interceptor to our main interceptor twice, once to requestInterceptors and once to responseInterceptors (or only to one of these if it intercepts only requests or only responses).
This example is far from complete. The benefit of this solution is that it adds the ability to add and remove interceptors without having to recreate the OkHttpClient instance. It requires extra work if you want to support retrying requests, for example.

Categories