Spring boot Oauth2 : Token relay from a client using Feign, Ribbon, Zull and Eureka to a ressource - spring-boot

I have an oauth2 client that get a token from an authorization server successfully. (not always has been the case but now it is... :))
The client, the zuul gateway and the resource server are all registered in Eureka.
My client use a Proxy to access to a remote ressource service named microservice-files.
#RestController
#FeignClient(name = "zuul-server")
#RibbonClient(name = "microservice-files")
public interface ProxyMicroserviceFiles {
#GetMapping(value = "microservice-files/root")
FileBean getUserRoot();
}
So I'd like to relay the token to Zull and then to the resource server.
I can relay the token this way to contact Zuul and apparently the load balancing is managed too (I've just test I didn't know and it's great) also zuul can relay the token, but it's not very convenient I'd prefer the previous approach.
#EnableConfigurationProperties
#SpringBootApplication
#EnableFeignClients("com.clientui")
public class ClientUiApplication {
#Bean
public OAuth2RestOperations restOperations(
OAuth2ProtectedResourceDetails resource,
OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
public static void main(String[] args) {
SpringApplication.run(ClientUiApplication.class, args);
}
}
here is the test controler
#Controller
public class ClientController {
#Autowired
private RestOperations restOperations;
#RequestMapping("/root")
public ResponseEntity userRootTest() {
String rootUrl = "http://localhost:9004/microservice-files/root";
return restOperations.getForEntity(rootUrl,FileBean.class);
}
}

If I correctly understand your problem then you can use a RequestInterceptor to add a token in each request by the feign. In order to do it you can use the next configuration:
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri("http://127.0.0.1:9000/auth/login");
resource.setUserAuthorizationUri("http://127.0.0.1:9000/auth/authorize");
resource.setClientId("my-client");
resource.setClientSecret("my-secret");
return resource;
}

This is what I did to make it work.
#Bean(name = "oauth2RestTemplate")
#LoadBalanced
public OAuth2RestTemplate restTemplate(SpringClientFactory clientFactory) {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails());
RibbonLoadBalancerClient ribbonLoadBalancerClient = new RibbonLoadBalancerClient(clientFactory);
LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(ribbonLoadBalancerClient);
ClientCredentialsAccessTokenProvider accessTokenProvider = new ClientCredentialsAccessTokenProvider();
accessTokenProvider.setInterceptors(Arrays.asList(loadBalancerInterceptor));
restTemplate.setAccessTokenProvider(accessTokenProvider);
return restTemplate;
}
public ClientCredentialsResourceDetails resourceDetails() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setId("1");
clientCredentialsResourceDetails.setClientId("my-ms");
clientCredentialsResourceDetails.setClientSecret("123");
clientCredentialsResourceDetails.setAccessTokenUri("http://oauth-server/oauth/token");
clientCredentialsResourceDetails.setScope(Arrays.asList("read"));
clientCredentialsResourceDetails.setGrantType("client_credentials");
return clientCredentialsResourceDetails;
}

Related

required a bean of type 'org.springframework.cloud.netflix.ribbon.SpringClientFactory' that could not be found

I have this test project which I would like to migrate to more recent version:
#Configuration
public class LoadbalancerConfig extends RibbonLoadBalancerClient {
public LoadbalancerConfig(SpringClientFactory clientFactory) {
super(clientFactory);
}
}
Full code example: https://github.com/rcbandit111/Generic_SO_POC/blob/master/src/main/java/org/merchant/database/service/sql/LoadbalancerConfig.java
Do you know how I can migrate this code to latest load balancer version?
I think examining the RibbonAutoConfiguration class gives you a good hint of how you should configure things.
First remove #Configuration from LoadbalancerConfig, I also renamed LoadbalancerConfig to CustomLoadbalancer to prevent confusion.
public class CustomLoadbalancer extends RibbonLoadBalancerClient {
public CustomLoadbalancer(SpringClientFactory clientFactory) {
super(clientFactory);
}
}
add the following dependency to your gradle
com.netflix.ribbon:ribbon:2.7.18
then add a configuration class like:
#Configuration
#ConditionalOnClass({Ribbon.class})
#AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
#ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
havingValue = "true", matchIfMissing = true)
#AutoConfigureBefore(LoadBalancerAutoConfiguration.class)
public class LoadBalancerClientConfig {
#Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
#Bean
public CustomLoadbalancer customLoadbalancer() {
return new CustomLoadbalancer(springClientFactory());
}
#Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
}
If you want to use Spring cloud load balancer instead of above configuration add spring-cloud-starter-loadbalancer dependency to your gradle.build and for configuration you only need this bean:
#LoadBalanced
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
}
This RestTemplate pretty works identical to standard RestTemplate class, except instead of using physical location of the service, you need to build the URL using Eureka service ID.
Here is an example of how could you possibly use it in your code
#Component
public class LoadBalancedTemplateClient {
#Autowired
RestTemplate restTemplate;
#Autowired
User user;
public ResponseEntity<Result> getResult() {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromHttpUrl("http://application-id/v1/") // application id registered in eureka
.queryParam("id", user.getUserId());
return restTemplate.getForEntity(uriComponentsBuilder.toUriString(),
Result.class);
}
}
Also if you wish to use reactive client the process is the same first define the bean:
#LoadBalanced
#Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
and then inject and use it when you need:
#Autowired
private WebClient.Builder webClient;
public Mono<String> doSomething() {
return webClient
.build()
.get()
.uri("http://application-id/v1/")
.retrieve()
.bodyToMono(String.class);
}
Also you can check documentation for additional information: documentation

OAuth2FeignRequestInterceptor class deprecated in Spring Boot 2.3

In our last feign client security configuration we have this Bean:
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(
ClientCredentialsResourceDetails oauth2RemoteResource) {
return new OAuth2FeignRequestInterceptor(
new DefaultOAuth2ClientContext(),
oauth2RemoteResource
);
}
In 2.3 spring version OAuth2FeignRequestInterceptor is deprecated! But we cannot found the new one.
Anyone knows something about that?
You can create your own RequestInterceptor to add the Authorization header.
There's an example here:
https://developer.okta.com/blog/2018/02/13/secure-spring-microservices-with-oauth
I had the same problem, I needed a request interceptor to call through a Feign client to a another microservice.
The idea is very easy, The only thing that I needed to implement was a custom RequestInterceptor annonted with #Component that inject the current JWT from the security context to the Authorization Header.
You can view this component as follows:
#Component
#Slf4j
public class FeignClientInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String TOKEN_TYPE = "Bearer";
#Override
public void apply(RequestTemplate requestTemplate) {
log.debug("FeignClientInterceptor -> apply CALLED");
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication instanceof JwtAuthenticationToken) {
final JwtAuthenticationToken jwtAuthToken = (JwtAuthenticationToken) authentication;
requestTemplate.header(AUTHORIZATION_HEADER, String.format("%s %s", TOKEN_TYPE, jwtAuthToken.getToken().getTokenValue()));
}
}
}
Next, I can use the feign client successfully
final APIResponse<ProcessedFileDTO> response = filesMetadataClient.getProcessedFileByName(uploadFile.getOriginalFilename());
if (response.getStatus() == ResponseStatusEnum.ERROR
&& response.getHttpStatusCode() == HttpStatus.NOT_FOUND) {
sendFileToSftp(uploadFile);
} else {
throw new FileAlreadyProcessedException();
}

Spring Boot OAuth2RestTemplate and HTTP Client Metrics

Actuator manages the instrumentation of RestTemplate, we only have to inject RestTemplateBuilder to create RestTemplate bean:
#Configuration
public class HttpClientConfiguration {
private final RestTemplateBuilder restTemplateBuilder;
// let Actuator manages the instrumentation
public HttpClientConfiguration(RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder;
}
#Bean // let Sleuth intercept requests
public RestTemplate createRestTemplate() {
return restTemplateBuilder.build();
}
}
How to create an OAuth2RestTemplate bean (built-in OAuth2) with the HTTP Client Mertics support?
I just found a solution:
#Configuration
#EnableOAuth2Client
public class OAuthClientConfig {
private final MetricsRestTemplateCustomizer metricsRestTemplateCustomizer;
public OAuthConfig(MetricsRestTemplateCustomizer metricsRestTemplateCustomizer) {
this.metricsRestTemplateCustomizer=metricsRestTemplateCustomizer;
}
#Bean
public OAuth2RestTemplate createOAuth2RestTemplate() {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(
getClientCredentialsResourceDetails(),
new DefaultOAuth2ClientContext());
metricsRestTemplateCustomizer.customize(restTemplate);
return restTemplate;
}
private ClientCredentialsResourceDetails getClientCredentialsResourceDetails() {
ClientCredentialsResourceDetails rd = new ClientCredentialsResourceDetails();
rd.setClientId("my-client-id");
rd.setClientSecret("my-client-secret");
rd.setAccessTokenUri("https://my-idp.server/oauth2/token");
return rd;
}
}
This is a late answer, but I was looking for something similar and found an alertnaive way.
The Spring Boot configured RestTemplateBuilder adds more than just the HTTP Client Mertics (see RestTemplateAutoConfiguration). If you don't want to lose those, you can still use the auto-configured RestTemplateBuilder to configure your OAuth2RestTemplate as follows:
#Configuration
public class OAuthClientConfig {
#Bean
public OAuth2RestTemplate createOAuth2RestTemplate(RestTemplateBuilder builder) {
// Create and setup your OAuth2RestTemplate:
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(....);
// let the auto-configure builder configure your template:
return builder.configure(restTemplate);
}
}

SPRING-WS No marshaller registered. Caused by SPRING IOC

I have a SOAP client service which works fine.
The SOAP headers and request are managed in a SOAPConnector class.
public class SOAPConnector extends WebServiceGatewaySupport {
public Object callWebService(String url, Object request) {
// CREDENTIALS and REQUEST SETTINGS...
return getWebServiceTemplate().marshalSendAndReceive(url, request, new SetHeader(requestHeader));
}
}
I'm receiving the requested Data once I call my (SoapConnector) service on the main Class.
#SpringBootApplication
public class SpringSoapSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSoapSecurityDemoApplication.class, args);
}
#Bean
public CommandLineRunner lookup(SOAPConnector soapConnector) {
return args -> {
String hotelCode = "****";
FutureBookingSummaryRequest request = new FutureBookingSummaryRequest();
FetchBookingFilters additionalFilters = new FetchBookingFilters();
// Some additionalFilters settings
request.setAdditionalFilters(additionalFilters);
FutureBookingSummaryResponse response = (FutureBookingSummaryResponse) soapConnector
.callWebService("MY WSDL URL", request);
System.err.println(response.getHotelReservations());
};
}
}
SO FAR IT WORKS FINE.
Then I've tried to create a separate service for the previous request.
BookingService.java
public class BookingService extends WebServiceGatewaySupport {
#Autowired
SOAPConnector soapConnector;
public String getReservations() {
String hotelCode = "****";
FutureBookingSummaryRequest request = new FutureBookingSummaryRequest();
FetchBookingFilters additionalFilters = new FetchBookingFilters();
// Some additionalFilters settings
request.setAdditionalFilters(additionalFilters);
FutureBookingSummaryResponse response = (FutureBookingSummaryResponse) soapConnector
.callWebService("MY WSDL URL", request);
System.err.println(response.getHotelReservations());
};}
In order to inject the SOAPCONNECTOR I've added #Service to SOAPCONNECTOR class , and #Autowired SOAPConnector soapConnector to the service calling it
Now once I call the created BookingService in the main class
#SpringBootApplication
public class SpringSoapSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSoapSecurityDemoApplication.class, args);
BookingService bookingService = new BookingService();
bookingService.getReservations();
}
}
The SOAPCONNECTOR stops working an I receive the following error :
No marshaller registered. Check configuration of WebServiceTemplate.
I'm pretty sure that's this issue is related to SPRING IOC , dependecy injection .. Since the SOAP service is well configured and working..
Note : I've checked this similiar question
Consuming a SOAP web service error (No marshaller registered. Check configuration of WebServiceTemplate) but the #Autowired didn't solve the issue.
Any help ?
In case someone is facing the same issue, it turned out that I've missed the #Configuration annotation on the beans configuration class. The right one should look like the following:
#Configuration
public class ConsumerConfig {
private String ContextPath = "somePackage";
private String DefaultUri = "someWsdlURI";
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this package must match the package in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath(ContextPath);
return marshaller;
}
#Bean
public SOAPConnector checkFutureBookingSummary(Jaxb2Marshaller marshaller) {
SOAPConnector client = new SOAPConnector();
client.setDefaultUri(DefaultUri);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}

Autowired RibbonAware RestTemplate with Interceptor

I am having a wrapper that autowire ribbon aware RestTemplate and then I add a interceptor-
#Component
public class MyRestTemplate {
#Autowired
private RestTemplate restTemplate;
#PostConstruct
public void init(){
restTemplate.setInterceptors(Collections.singletonList(new
MyRestTemplateRequestInterceptor(applicationName)));
}
}
If I #AutoWire RestTemplate in some other component, will I get same instance of RestTemplate with this incterceptor? My observation is the autowired resttemplate in other component also adds this interceptor. Please help to validate the understanding.
Yyou will get the same instance of RestTemplate. When the bean MyRestTemplate is constructor (rather after it's construction), you are adding the MyRestTemplateRequestInterceptor to the RestTemplate interceptor.
So this interceptor will be applied to RestTemplate.
If you are looking to apply an interceptor for a specific feign client you can create your own configuration:
#Configuration
#IgnoreDuringScan
public class FeignClientConfiguration {
#Autowired
private Environment environment;
#Bean
public Encoder feignEncoder() {
return new GsonEncoder();
}
#Bean
public ErrorDecoder feignDecoder() {
return new GsonDecoder();
}
#Bean
public RequestInterceptor requestHeaderInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
//impl
}
};
}
#Bean
public Feign.Builder feingBuilder() {
return Feign.builder().encoder(feignEncoder()).decoder(feignDecoder()).errorDecoder(new YourErrorDecoder())
.requestInterceptor(requestHeaderInterceptor);
}
}
Then just apply the configuration to your feign client:
#FeignClient(name = "myFeign", url = "url/...", configuration = {
FeignClientConfiguration.class
})
public interface YourFeignClient {
//....
}

Resources