Using #FeignClient with OAuth2Authentication in Javaclient - spring-boot

I would like to use a #FeignClient in a simple spring boot application (CommandLineRunner) to call a microservice endpoint. How can I provide an OAuth2Authentication to call a protected endpoint like helloUser() ?
#FeignClient(name = "sampleService", contextId = "greetingService")
public interface GreetingService {
#GetMapping("/hello-anonymous")
String helloAnonymous();
#GetMapping("/hello-user")
#Secured({ Role.USER })
String helloUser();
#GetMapping("/hello-admin")
#Secured({ Role.ADMIN })
String helloAdmin();
}

You can use Feign RequestInterceptor to pass the auth header downstream:
public class FeignRequestInterceptor implements RequestInterceptor {
#Override
public final void apply(RequestTemplate template) {
template.header("Authorization", "foo token");
}
}
This way all the feign calls will be provisioned with an auth header.

Related

How can I use RequestHeader with actuator endpoint?

I have customized my actuator/info endpoint and I want to use information from the header to authorize a RestTemplate call to another service.
I am implementing the InfoContributor as here:
https://www.baeldung.com/spring-boot-info-actuator-custom
I want to accept request headers in the contribute() method. For any user defined REST endpoint, I can define a #RequestHeader parameter and access headers.
But unfortunately, the InfoContributor's contribute() method takes only one parameter.
How can I access a request header inside the contribute() method?
You can autowire HttpServletRequest into your InfoContributor
import javax.servlet.http.HttpServletRequest;
#Component
public class Custom implements InfoContributor {
#Autowired
private HttpServletRequest request;
#Override
public void contribute(Info.Builder builder) {
...
request.getHeader("your header");
...
}
}
Or you can use RequestContextHolder to get hold of it
#Component
public class Custom implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
...
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
request.getHeader("your header");
...
}
}

ConversationScoped in Quarkus

I am migrating an application from Thorntail to Quarkus. It uses the the conversation scope annotation in a bean that provides the token information during all the rest api request to any service interested in it. But in Quarkus documentation it says the conversation scope is not implemented. Is there a similar feature I can use?
Here is what I want to do:
#Path
#ApplicationScoped
public class FruitsResource {
#Inject FruitsService fruitsService;
#POST
public int post (Fruit fruit) {
return fruitsService.post(fruit);
}
}
#Provider
#ApplicationScoped
private class AuthorizationFilter implements ContainerRequestFilter {
#Inject AuthorizationHolder authorizationHolder;
#Override
public void filter (ContainerRequestContext request) {
String token = request.getHeaderString(HttpHeaders.AUTHORIZATION);
Authorization authorization = createAuthorizationFromToken(token);
authorizationHolder.setAuthorization(authorization);
}
}
#ConversationScoped
private class AuthorizationHolder {
private Authorization authorization;
#Produces
public Authorization getAuthorization () {
return authorization;
}
public void setAuthorization (Authorization authorization) {
this.authorization = authorization;
}
}
#ApplicationScoped
private class FruitsService {
#Inject Authorization authorization;
#Inject EntityManager entityManager;
#Transactional
public void post (Fruit fruit) {
// do some complex validation with the authorization object
...
// persist object
entityManager.persist(fruit);
entityManager.flush();
return fruit.getId();
}
}
Is the Authorization header present in each request? I suppose it is (or should be), in which case just using #RequestScoped instead of #ConversationScoped should work. This is probably the best thing to do, anyway.
In case the header is only present in "first" request and subsequent requests in the same session can reuse the token, then you can just replace #ConversationScoped with #SessionScoped. I think enforcing the header to be present in all requests would be better, though.
Finally, if you'd really like to emulate conversations, you can do something like this (not tested, not even written in an IDE, just from the top of my head):
#SessionScoped
private class AuthorizationHolder {
private ConcurrentMap<String, Authorization> authorizations = new ConcurrentHashMap<>();
public Authorization getAuthorization(ContainerRequestContext request) {
return authorizations.get(getConversationId(request));
}
public void setAuthorization(ContainerRequestContext request, Authorization authorization) {
this.authorizations.put(getConversationId(request), authorization);
}
private String getConversationId(ContainerRequestContext request) {
MultivaluedMap<String, String> query = request.getUriInfo().getQueryParameters();
return query.getFirst("cid");
}
}
However, as I said above, I really think you should make the bean #RequestScoped and force the clients to send the Authorization header with each request.

FeignClient is passing on headers

I have about 10 microservices all built with Spring boot 2 using Eureka and FeignClients. My microservices use certain header values to keep track of data so when a FeignClient is used it needs to pass on certain values that are in the incoming request. So if Microservice 1 does a call to Microservice 2 it must pass on the headers from the incoming request onto microservice 2. I haven't been able to find out how I can do that. I understand their is #Header however if you have 20 FeignClients then you don't want to have to manually add the #header to all the FeignClients. Can you indicate that FeignClients must read a certain header from the incoming request and pass it on in the FeignClient?
You can use request interceptor in Feign.
Example Implementation:
Request Interceptor:
#Component
public class MyRequestInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String authorization = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
if(null != authorization) {
template.header(HttpHeaders.AUTHORIZATION, authorization);
}
}
}
Bean Configuration:
#Configuration
public class CustomFeignConfig {
#Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
#Bean
public MyRequestInterceptor basicAuthRequestInterceptor() {
return new MyRequestInterceptor();
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
}

Spring Cloud Gateway pass bean to custom filter

We are attempting to use Spring Cloud Gateway to setup a microservice based architecture. Currently, we have defined a route programatically:
#ServletComponentScan
#SpringBootApplication
public class GatewayApplication {
// to be passed to and used by custom filter
#Autowired
RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("status", r -> r
.method(HttpMethod.GET)
.and()
.path("/status")
.filters(f -> f.rewritePath("/status", "/v2/status")
.filter(new AuthorizationFilter(restTemplate).apply(new Config(""))))
.uri("http://localhost:8081/"))
.build();
}
}
The above would route an incoming request /status via GET to another endpoint. We would like to apply a custom filter, which we have implemented in AuthorizationFilter. This filter, as the name implies, is another microservice which will either allow or deny an incoming request based on credentials and permissions.
Currently, the pattern we are following, which works, is to inject a Spring RestTemplate into the gateway class above, and then to pass this RestTemplate to the constructor of the filter.
However, how can this be done if we wanted to switch to using a YAML file for defining all the routes? Presumably in both cases Spring would be constructing a new filter for each incoming request. But in the case of YAML, how can we pass something in the construtor? If this cannot be done, is there any other way to inject a RestTemplate, or any other resource into a custom Spring gateway filter?
You can register your own custom GatewayFilterFactory. This allows you to provide a custom configuration, and within that configuration, you can use SpEL to reference a bean.
For example:
#Component
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {
public AuthenticationGatewayFilterFactory() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
// TODO: Implement
}
public static class Config {
private RestTemplate restTemplate;
// TODO: Getters + Setters
}
}
Now you can use SpEL to properly reference a RestTemplate bean:
spring:
cloud:
gateway:
routes:
- id: status
uri: http://localhost:8081/
filters:
- name: Authentication
args:
restTemplate: "#{#nameOfRestTemplateBean}"
predicates:
- Path=/status
Alternatively, you could inject a RestTemplate bean within your gateway filter. For example:
#Component
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {
private RestTemplate restTemplate;
public AuthenticationGatewayFilterFactory(RestTemplate restTemplate) {
super(Config.class);
this.restTemplate = restTemplate;
}
#Override
public GatewayFilter apply(Config config) {
// TODO: Implement
}
public static class Config {
// TODO: Implement
}
}
The code/configuration necessary to do the inject is less complex, but it also makes it more difficult if you ever decide to put AuthenticationGatewayFilterFactory in a separate library, as the "consumers" of this library won't have any control over which RestTemplate is being injected.

How to get Spring REST to lead all endpoints with /v1/ after the domain and port?

How do I make a Spring REST service assign a value after the domain and port, but before the mapped endpoint? For example: http://{domain}/v1/...?
Example:
#RestController
#RequestMapping("home")
public class HomeController {
#RequestMapping("number")
public ResponseEntity getNumber() {
return ResponseEntity.ok(1);
}
}
curl http://localhost:8080/v1/home/number ~ 1
Also, is there a name for what I'm trying to achieve?
Assuming you're using Spring Data Rest, you can configure it in the RepositoryRestConfiguration #Configuration class like so:
public class SDRConfig extends RepositoryRestMvcConfiguration {
#Override
public RepositoryRestConfiguration config() {
RepositoryRestConfiguration config = super.config();
config.setBasePath("/v1");
return config;
}
}
Alternatively, with Spring Boot I believe it's
spring.data.rest.baseUri=api

Resources