How to retrieve the configured base URL from the Spring WebClient? - java

The Spring reactive WebClient can be built with a base URL:
import org.springframework.web.reactive.function.client.WebClient;
...
#Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.baseUrl("http://example.org")
.build();
// or alternatively a shortcut
// return WebClient.create("http://example.org");
}
Is there a way to retrieve the configured base URL back from an already existing WebClient instance?
Something like:
#Autowired
private WebClient webClient;
...
String baseUrl = webClient.getBaseUrl(); // I want to know how this WebClient is configured
assertEquals("http://example.org", baseUrl);
Or something like
var configuration = webClient.getConfiguration();
String baseUrl = configuration.getBaseUrl();
assertEquals("http://example.org", baseUrl);

WebClient is an interface with no API to get back the base URL, so maybe some implementation class has one but that will make your code dependent on the chosen one.

Related

How to Retrieve OAuth2AuthorizedClient in a request when using WebFlux

I have a back-end(Springboot) application that is connected to Azure AD and a front-end application that accesses it. In the front-end, I am requiring the user to authenticate using MSAL and passing this authentication to the BE using the On-Behalf-Of flow.
In the front-end, when I am trying to specify the registered client I simple use:
#RegisteredOAuth2AuthorizedClient("back-end") OAuth2AuthorizedClient authorizedClient
I'm trying to create another back-end application that my existing back-end will call and pass the authentication using OBO flow. To check the difference between the initial token from the user and the token the BE will provide to the new BE application, I created a log that fetch the token from these client like authorizedClient.getAccessToken().getTokenValue().
Now that I don't want the explicit approach and want only to add directly in the webclient request the .attributes(clientRegistrationId("new-back-end")), is there any way to check the token? Or at least get the OAuth2AuthorizedClient from the request?
Sample code:
webClient.get()
.uri(new URI(resourceBaseUri + resourceEndpoint))
.attributes(clientRegistrationId("new-be-app"))
.retrieve()
.bodyToMono(String.class)
.block();
• You can do the same as desired by you by using the ‘ServerOAuth2AuthorizedClientExchangeFilterFunction’ to determine the client to use by resolving the ‘OAuth2AuthorizedClient’ from the ‘ClientRequest.attributes()’. The following code shows how to set an ‘OAuth2AuthorizedClient’ as a request attribute: -
#GetMapping("/")
public Mono<String> index(#RegisteredOAuth2AuthorizedClient("okta")
OAuth2AuthorizedClient authorizedClient) {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
Note: - ‘oauth2AuthorizedClient()’ is a static method in ‘ServerOAuth2AuthorizedClientExchangeFilterFunction’.
Also, please note that the following code shows how to set the ‘ClientRegistration.getRegistrationId()’ as a request attribute: -
#GetMapping("/")
public Mono<String> index() {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta"))
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
You can use the code below also for your purpose: -
#Component
#RequiredArgsConstructor
public class OAuth2Utils {
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
public Mono<OAuth2AuthorizedClient> extractOAuth2AuthorizedClient(ServerRequest request) {
return request.principal()
.filter(principal -> principal instanceof OAuth2AuthenticationToken)
.cast(OAuth2AuthenticationToken.class)
.flatMap(auth -> authorizedClientRepository.loadAuthorizedClient(auth.getAuthorizedClientRegistrationId(), auth, request.exchange()));
}
}
Please find the links below for more information: -
How to access OAuth2AuthorizedClient in Webflux Functional Endpoints?
https://docs.spring.io/spring-security/reference/reactive/oauth2/client/authorized-clients.html#_providing_the_authorized_client

Feign client always null when being added through a library, how can I fix it?

I'have lately working with feign on a project where I decided will package it and ship it as a library so it will be easier to use. All things worked fine but at the part where I invoke the feign client it just returns a NPE.
I am wondering if Im missing a configuration, or this is because the fact its a library? or how it is included in the project?
Basically I have something like this (in the library):
On a LibraryConfiguration.java
//some more beans
#Bean
public ClientService clientService(){
return new ClientService();
}
at ClientService I have
#Autowired
private MyClient myClient;
ClientService(){
myClient.myResource();
}
At MyClient:
#FeignClient(name = "auth", url = "${my-url}", configuration = MyClient.Configuration.class)
public interface MyClient {
#RequestMapping(method = RequestMethod.POST, value = "/my-resource", consumes = "application/x-www-form-urlencoded")
String getResource(Map<String, ?> formParams);
class Configuration {
#Bean
Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> converters) {
return new SpringFormEncoder(new SpringEncoder(converters));
}
}
}
So it was all packaged and shipped.
At my app I have:
#SpringBootApplication
#Import(LibraryConfiguration.class)
#EnableFeignClients()
#Configuration
#ImportAutoConfiguration(FeignAutoConfiguration.class)
public class MyApiApplication {
....
}
I added the library like this (Im using gradle)
compile 'com.my-library:version'
Any idea of what could be missing?
Thanks.
Make sure the package level of the feign client is below the package level of the Main class(MyApiApplication).

Why are the Keycloak security constraints not active in #SpringBootTest and how can I activate them?

I'm in the process of building a new microservice and securing it with access tokens from Keycloak. So far I've been successful, the endpoint /token/test is only accessible with a valid token from Keycloak, the application properties look like this:
keycloak.auth-server-url=http://localhost:8888/auth
keycloak.realm=realm
keycloak.resource=api
keycloak.public-client=true
keycloak.securityConstraints[0].authRoles[0]=basic-token
keycloak.securityConstraints[0].securityCollections[0].name=secured by basic access token
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/*
This is working fine when starting the project with mvn spring-boot:run (I'm using spring-boot-starter, keycloak-spring-boot-starter and without spring-boot-starter-security which I want to avoid if possible.
Now I'm writing some tests for the fun of it and Keycloak's security constraints are simply not working. I've followed the test setup from https://github.com/cremich/testcontainers-keycloak (with updated versions and JUnit 5), the only difference being that the example is doing a lot of Keycloak setup by hand using spring-boot-starter-security. Why does it only work in tests when done with -security and why does my way not seem to work?
Am I missing something?
Thanks.
Edit: Example project
After some hours of debugging, I finally figured it out. The problem is that Keycloak's authentication is (for whatever reason, lol) done in a Tomcat valve, not in a filter. MockMvc doesn't go through the servlet container (and its valves), so it never even reaches the point where it would be authenticated.
TestRestTemplate does, though (and if you use starter-security, it is also a filter not a valve). I don't know the design decision behind using a valve and not a filter but you can either use the configuration from keycloak-starter and test it with a TestRestTemplate or use the more 'expensive' starter-security configuration in combination with MockMvc.
You need to set the authentication in SecurityContext with a mock or instance of the right type in each test: SecurityContextHolder.getContext().setAuthentication(authentication)
I wrote a set of libs to ease this. It includes a #WithMockKeycloackAuth annotation, along with Keycloak dedicated MockMvc request post-processor and WebTestClient configurer / mutator
Sample #WithMockKeycloackAuth usage:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = GreetingController.class)
#Import({
ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class,
KeycloakSpringBootSampleApp.KeycloakConfig.class })
// because this sample stands in the middle of non spring-boot-keycloak projects, keycloakproperties are isolated in
// application-keycloak.properties
#ActiveProfiles("keycloak")
public class GreetingControllerAnnotatedTest {
private static final String GREETING = "Hello %s! You are granted with %s.";
#MockBean
MessageService messageService;
#MockBean
JwtDecoder jwtDecoder;
#Autowired
MockMvcSupport api;
#Before
public void setUp() {
when(messageService.greet(any())).thenAnswer(invocation -> {
final var auth = invocation.getArgument(0, Authentication.class);
return String.format(GREETING, auth.getName(), auth.getAuthorities());
});
}
#Test
#WithMockKeycloakAuth
public void whenAuthenticatedWithoutAuthorizedPersonnelThenSecuredRouteIsForbidden() throws Exception {
api.get("/secured-route").andExpect(status().isForbidden());
}
#Test
#WithMockKeycloakAuth({ "AUTHORIZED_PERSONNEL" })
public void whenAuthenticatedWithAuthorizedPersonnelThenSecuredRouteIsOk() throws Exception {
api.get("/secured-route").andExpect(status().isOk());
}
#Test
#WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
id = #IdTokenClaims(sub = "42"),
oidc = #OidcStandardClaims(
email = "ch4mp#c4-soft.com",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy"),
accessToken = #KeycloakAccessToken(
realmAccess = #KeycloakAccess(roles = { "TESTER" }),
authorization = #KeycloakAuthorization(
permissions = #KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra"))),
privateClaims = #ClaimSet(stringClaims = #StringClaim(name = "foo", value = "bar")))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
api.get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")))
.andExpect(content().string(containsString("TESTER")));
}
}
Different libs are available from maven-central, choose one of following according to your use-case:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-addons</artifactId>
<version>2.3.4</version>
<scope>test</test>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>2.3.4</version>
<scope>test</test>
</dependency>

Feign Registration - Spring Cloud - Change Target without ribbon over-ride

Introduction
I would like to be able to have two different spring profiles, and depending on the profile to change to a hardcoded address for our feign builders.
Currently was have the following:
return builder.target(cls, "http://" + serviceName);
But I would actually like to do the following and over-ride the address:
return builder.target(cls, "http://our-server:8009/" + serviceName);
Why
Sometimes we don't want to run all the services within our development environment. Additionally, some of the services are only available through a zuul gateway sometimes.
So we run the same code in different situations and conditions.
Technical Details
We have the following code that we use for building our Feign Clients.
We had been using the #FeignClient annotation in the past, but lately we decided to start building our feignClients manually.
Example below:
#FeignClient(name = "ab-document-store", configuration = MultiPartSupportConfiguration.class, fallback = DocumentStoreFallback.class)
We call the feignRegistrar class with the following command:
return registerFeignClient(DocumentStoreClient.class, true);
#RequiredArgsConstructor
//#Component
#Slf4j
public class FeignRegistrar {
#Autowired
private Decoder decoder;
#Autowired
private Encoder encoder;
#Autowired
private Client client;
#Autowired
private Contract feignContract;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Autowired
private List<RequestInterceptor> interceptors;
public <T> T register(Class<T> cls, String serviceName, boolean isDocumentStore) {
if(isDocumentStore){
encoder = new MultipartFormEncoder(new SpringEncoder(messageConverters));
}
//Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
for(RequestInterceptor interceptor : interceptors) {
builder.requestInterceptor(interceptor);
}
log.debug("Registering {} - as feign proxy ", serviceName);
return builder.target(cls, "http://" + serviceName);
}
public static class Slf4Logger extends Logger {
#Override
protected void log(String configKey, String format, Object... args) {
log.info("{} - {}", configKey, args);
}
}
}
Spring Cloud Property Over-ride
We have also been using property files such as application-ENV.property with entries such as the following:
ab-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
ab-document-store.ribbon.listOfServers: localhost:8025
Unfortunately, listOfServers is not enough for us. We would like to be able to assign a directory/path as well. Something like:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
Otherworkaround
I have thought about sneaking in a header into all requests such as X-SERVICE-NAME using a feign interceptor. Then we could point all services to an address (e.g. localhost:9001) , and forward/proxy those requests to localhost:9001/X-SERVICE-NAME.
However, I would prefer a much easier solution such as:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
But this doesn't work :(
Introduction
I found a solution for this using a proxy that detects a header.
So, I have a feign interceptor on the java-side that attaches a header x-service-name to every feign-request.
I also have a NodeJS proxy, that analyzes requests, finds x-service-name, and re-writes the requests to become: x-service-name/originalRequestPath.
This allows me to have all the microservices behind a zuul gateway but also access them using a eureka-over-ride.
Java-Feign-Interceptor
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(usedEncoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
NodeJS proxy
In the example, my zuul gateway ( or another proxy ) is on localhost:9001.
I'm listening on localhost:1200 .
let enableProxyForJava = process.env.ENABLE_PROXY_FOR_JAVA;
if (enableProxyForJava != undefined && enableProxyForJava.toLowerCase() === 'true') {
var httpProxyJava = require('http-proxy');
var proxyJava = httpProxyJava.createProxy();
gutil.log( gutil.colors.green('Enabling Proxy for Java. Set your Eureka overrides to localhost:1200.') );
require('http').createServer(function(req, res) {
console.log("req.headers['x-service-name'] = " + req.headers['x-service-name']);
console.log("Before req.url:"+ req.url);
if( req.headers['x-service-name'] != undefined){
let change = req.headers['x-service-name'] +req.url;
console.log("After req.url:"+ change);
req.url = change;
}
proxyJava.web(req, res, {
target: 'http://localhost:9001/'
});
}).listen(1200);
}
Property file inside Java Application that has feign clients
mbak-microservice1.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice1.ribbon.listOfServers: localhost:1200
mbak-microservice2.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice2.ribbon.listOfServers: localhost:1200
mbak-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-document-store.ribbon.listOfServers: localhost:1200

OAuth 2 spring Webclient for password grant

In the Rest template I was able to to do the following. I'm trying to implement the same using spring webclient and so far I couldn't find any documentation on how to set this up.
#Bean
public OAuth2RestTemplate createRestTemplate() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setAccessTokenUri(accessTokenUri);
resource.setClientId(clientID);
resource.setClientSecret(clientSecret);
resource.setGrantType("password");
resource.setScope(Arrays.asList(scope));
resource.setUsername("user");
resource.setPassword("pass");
return new OAuth2RestTemplate(resource);
}

Categories

Resources