Spring Webflux and Keycloak JWT rest api - java

I am building a userinfo endpoint on my Webflux rest api, how do I access the access_token passed in through the Authorization header in the rest call. Also need a similar endpoint to update the user.
All the examples I have found with latest spring 5/boot 2 are about securing a webapp.
#GetMapping("/api/user-info")
public Map userInfo(OAuth2AuthenticationToken authentication) {
OAuth2AuthorizedClient authorizedClient = this.getAuthorizedClient(authentication);
Map userAttributes = Collections.emptyMap();
String userInfoEndpointUri = authorizedClient
.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUri();
if (!StringUtils.isEmpty(userInfoEndpointUri)) {
// userInfoEndpointUri is optional for OIDC Clients
userAttributes = WebClient.builder()
.filter(oauth2Credentials(authorizedClient))
.build()
.get()
.uri(userInfoEndpointUri)
.retrieve()
.bodyToMono(Map.class)
.block();
}
return userAttributes;
}
private OAuth2AuthorizedClient getAuthorizedClient(OAuth2AuthenticationToken authentication) {
return this.authorizedClientService.loadAuthorizedClient(
authentication.getAuthorizedClientRegistrationId(), authentication.getName());
}
private ExchangeFilterFunction oauth2Credentials(OAuth2AuthorizedClient authorizedClient) {
return ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> {
ClientRequest authorizedRequest = ClientRequest.from(clientRequest)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authorizedClient.getAccessToken().getTokenValue())
.build();
return Mono.just(authorizedRequest);
});
}
OAuth2AuthenticationToken object defined in the method is null which is understandable but not sure what else need configuring.
Thanks for your help.

Related

Java Spring boot how to manage 2 exchange server resources with OAUth2 client on the other?

I am trying to add an OAuth2 token to my requests in order to secure exchanges. With another server.
spec spring-boot-starter-oauth2-client
I know that this code does not work, but I have not been able to find complete examples
add a header request token to access another secure server
#Configuration
public class OAuth2SecurityConfigClient {
#Autowired
private WebClient webClient;
public void logResourceServiceResponse() {
webClient.get()
.uri("{$spring.security.oauth2.client.provider.idsvr.issuer-uri}")
.retrieve()
.bodyToMono(String.class)
.map(string
-> "Retrieved using Client Credentials Grant Type: " + string)
.subscribe(logger::info);
}
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId("idsvr");
return WebClient.builder()
.filter(oauth)
.build();
}
}
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Barear" + webclient.getToken());
public String[] getUserAll() {
return localApiClient
.head(headers )
.get()
.uri(baseUrl+"/users" )
.retrieve()
.bodyToMono(String[].class)
.block(REQUEST_TIMEOUT);
}
spring.security.oauth2.client.registration.idsvr.client-name=client
spring.security.oauth2.client.registration.idsvr.client-id=KEY
spring.security.oauth2.client.registration.idsvr.client-secret=SKEY
spring.security.oauth2.client.registration.idsvr.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.idsvr.scope=openid
spring.security.oauth2.client.provider.idsvr.issuer-uri=XXX/idserver
spring.security.oauth2.client.provider.idsvr.authorization-uri=XXX/idserver/connect/authorize
spring.security.oauth2.client.provider.idsvr.token-uri=XXX/idserver/connect/token
spring.security.oauth2.client.provider.idsvr.jwk-set-uri=XXX/idserver/.well-known/jwks
to sum up, I therefore want to add to all my applications calling a 2nd server a valid authentication token of the bearen type with OAuth2
Is you resource-server 1 accessing resources from resource-server 2 in its own name or on behalf of an authenticated user?
The answer to this question will determine the flow to use:
in first case resource-server 1 is actually a client and has to contact authorization-server to get an access-token of its own using client-credentials flow.
in second case, resource-server 1 can authorize its own request to resource-server 2 with the Bearer access-token it recieved (and which should be available from Authentication instance in security-context)
Of course the configuration to apply is quite different depending on your actual case.
Configuring WebClient with client-credentials
spring.security.oauth2.client.provider.idsvr.issuer-uri=XXX/idserver
spring.security.oauth2.client.registration.idsvr.client-id=KEY
spring.security.oauth2.client.registration.idsvr.client-secret=SKEY
spring.security.oauth2.client.registration.idsvr.authorization-grant-type=client_credentials
webClient.get()
.uri(baseUrl + "/users")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("idsvr"))
.retrieve()
....
Forward access-token
final var auth = (AbstractOAuth2TokenAuthenticationToken<OAuth2Token>) SecurityContextHolder.getContext().getAuthentication();
webClient.get()
.uri(baseUrl + "/users")
.headers(headers -> headers.setBearerAuth(auth.getToken().getTokenValue()))
.retrieve()
....

How to perform a refresh with spring-boot-starter-oauth2-client

I'm using spring-boot-starter-oauth2-client to authenticate my user with Google. This works well and I can sign in and get valid access and refresh token as expected.
I'm creating the access token as such:
public class TokenServiceImpl implements TokenService {
private final OAuth2AuthorizedClientService clientService;
#Override
public GoogleCredentials credentials() {
final var accessToken = getAccessToken();
return getGoogleCredentials(accessToken);
}
private GoogleCredentials getGoogleCredentials(String accessToken) {
return GoogleCredentials
.newBuilder()
.setAccessToken(new AccessToken(accessToken, null))
.build();
}
private String getAccessToken() {
final var oauthToken = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
return clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName()).getAccessToken().getTokenValue();
}
}
The token is ultimately being used in the Google Photo API client as such
private PhotosLibraryClient getClient() {
final var settings =
PhotosLibrarySettings
.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(tokenService.credentials()))
.build();
return PhotosLibraryClient.initialize(settings);
}
The problem is that the token will expire after a short period and I'd like to refresh it to keep it active.
I'm unsure what pattern of methods I can use to do this, without having to write the entire OAuth flow (defeating the purpose of something like the Spring oauth2-client).
So far I have no other token/security/filter logic in my application.
Do I just need to write it all out manually, or is there another way I can do this?
The OAuth2AuthorizedClientManager will take care of refreshing your access token for you, assuming you get a refresh token along with your access token. The doco for OAuth2AuthorizedClientManager is at
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client
When configuring your OAuth2AuthorizedClientManager, make sure you have included refreshToken in the OAuth2AuthorizedClientProvider...
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
You then use the OAuth2AuthorizedClientManager to get the access token. The sample from the spring doco is below...
#Controller
public class OAuth2ClientController {
#Autowired
private OAuth2AuthorizedClientManager authorizedClientManager;
#GetMapping("/")
public String index(Authentication authentication,
HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
.principal(authentication)
.attributes(attrs -> {
attrs.put(HttpServletRequest.class.getName(), servletRequest);
attrs.put(HttpServletResponse.class.getName(), servletResponse);
})
.build();
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
...
return "index";
}
}
If the current accessToken has expired, this will automatically request a new accessToken using the previously obtained refreshToken.

Spring WebClient Retry Logic with new Headers

I am trying to build a retry logic using Spring WebClient. The problem that I am trying to solve is very simple. I am calling an API endpoint to get some values. If the API returns an error with say 401 response, then I will have to make call to Token service and renew my token and use the new token and make the same API call.
The general psudo is
try {
GET /locations data
} catch(401 Unauthorized) {
POST /token and get renew Token --> This is another WebClient API call With New Token
call again GET /locations and return value
} catch (Another Exception) {
throw Application Error
}
Here is the Spring code that I am trying to do and it does not look like it is working.
Any suggestion on how to do it.
public List<Location> getLocations(final User user) {
if (null == user) {
throw new ApplicationException("User cannot be null");
}
if (null == user.getHoneyWellLinkToken()) {
throw new ApplicationException(String.format("%s has not linked the account with Honeywell", user.getUsername()));
}
List<Location> locations = getLocationsAPI(user).block();
return locations;
}
private Mono<List<Location>> getLocationsAPI(final User user) {
String endpoint = config.getApi().getLocationsEndpoint()
.concat("?apikey=")
.concat(config.getCredentials().getClientId());
return WebClient.builder().baseUrl(endpoint)
.build()
.get()
.headers(httpHeaders -> httpHeaders.setBearerAuth(user.getHoneyWellLinkToken().getAccessToken()))
.retrieve()
.bodyToFlux(Location.class)
.collectList()
.doOnError(err -> {
WebClient.builder().baseUrl(endpoint)
.build()
.get()
.headers(httpHeaders -> httpHeaders.setBearerAuth(honeywellService.renewToken(user).block().getHoneyWellLinkToken().getAccessToken()))
.retrieve().bodyToFlux(Location.class);
});
}
This code is hosted on GitHub https://github.com/reflexdemon/home-use/blob/main/src/main/java/io/vpv/homeuse/service/HoneywellThermostatService.java
Use onErrorResume instead of doOnError
Do not block when renewing token
private Mono<List<Location>> getLocationsAPI(final User user) {
String endpoint = config.getApi().getLocationsEndpoint()
.concat("?apikey=")
.concat(config.getCredentials().getClientId());
return getLocations(endpoint, user)
.onErrorResume(err -> honeywellService.renewToken(user)
.flatMap(newUser -> getLocations(endpoint, newUser)));
}
private Mono<List<Location>> getLocations(String endpoint, User user) {
return WebClient.builder()
.baseUrl(endpoint)
.build()
.get()
.headers(httpHeaders -> httpHeaders.setBearerAuth(user
.getHoneyWellLinkToken()
.getAccessToken()))
.retrieve()
.bodyToFlux(Location.class)
.collectList();
}
Also, it's a good idea to use a single WebClient instance instead of building a new one for each request.

Replace Spring KerberosRestTemplate with new WebClient APIs

I am trying to replace existing Spring KerberosRestTemplate with WebClient APIs.
So is there any support provided for Kerberos in new WebClient APIs?
any help will be appreciated even pointing to some tutorial/doc will be helpful.
You need to create an ExchangeFilterFunction implementation which checks for the WWW-Authenticate header and then re-sends the request with an Authorization header.
#Override
public Mono<ClientResponse> filter(final ClientRequest request, final ExchangeFunction next) {
return next.exchange(request)
.flatMap(response -> {
final Set<String> headerValues = Sets.newLinkedHashSet(response.headers().header(HttpHeaders.WWW_AUTHENTICATE));
if (headerValues.contains("Negotiate")) {
final String authHeader = doAs(new CreateAuthorizationHeaderAction(userPrincipal, "HTTP/" + request.url().getHost()));
final ClientRequest authenticatedRequest = ClientRequest.from(request)
.header(HttpHeaders.AUTHORIZATION, "Negotiate " + authHeader)
.build();
return next.exchange(authenticatedRequest);
}
return Mono.just(response);
});
}
You can lift the implementation for CreateAuthorizationHeaderAction here.

Replacing RestTemplate with WebClient

I have a controller that uses RestTemplate to get data from several rest endpoints. Since RestTemplate is blocking, my web page is taking long time to load. In order to increase the performance, I am planning to replace all my usages of RestTemplate with WebClient. One of the methods I currently have that uses RestTemplate is as below.
public List<MyObject> getMyObject(String input){
URI uri = UriComponentsBuilder.fromUriString("/someurl")
.path("123456")
.build()
.toUri();
RequestEntity<?> request = RequestEntity.get(uri).build();
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() {};
ResponseEntity<List<MyObject>> responseEntity = restTemplate.exchange(request, responseType);
MyObject obj = responseEntity.getBody();
}
Now I want to replace my above method to use WebClient but I am new to WebClient and not sure where to start. Any direction and help is appreciated.
To help you I am giving you example how we can replace restTemple with webClient. I hope you have already setup your pom.xml
Created a Configuration class.
#Slf4j
#Configuration
public class ApplicationConfig {
/**
* Web client web client.
*
* #return the web client
*/
#Bean
WebClient webClient() {
return WebClient.builder()
.filter(this.logRequest())
.filter(this.logResponse())
.build();
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
log.info("WebClient request: {} {} {}", clientRequest.method(), clientRequest.url(), clientRequest.body());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.info("WebClient response status: {}", clientResponse.statusCode());
return Mono.just(clientResponse);
});
}
}
Plus a service class calling WebClient
#Component
#RequiredArgsConstructor
public class MyObjectService {
private final WebClient webClient;
public Mono<List<Object>> getMyObject(String input) {
URI uri = UriComponentsBuilder.fromUriString("/someurl")
.path("123456")
.build()
.toUri();
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() {
};
return this.webClient
.get()
.uri(uri)
.exchange()
.flatMap(response -> response.bodyToMono(responseType));
}
}
This will give you a non blocking Mono of List<MyObject>, you can also extract body to flux by using response.bodyToFlux(responseType)
I hope this will give you a base to explore more.

Categories