How can I write intetgration tests Spring Web Client(Spring MVC) with Oauth2 when using Credentials Flow - spring-boot

I have an Oauth 2 client that actually interacts with another microservice that acts as an authorization server (auth-server).
I have an endpoint (use spring mvc). It has the annotation
#PreAuthorize("has Scope(T(.........).
#Configuration
public class AuthWebClientConfiguration {
#Bean
public OAuth2AuthorizedClientManager authorizedManager(
ClientRegistrationRepository client,
OAuth2AuthorizedClientRepository authorizedClient
) {
OAuth2AuthorizedClientProvider authorizedProvider =
OAuth2AuthorizedClientProviderBuilder
.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedManager =
new DefaultOAuth2AuthorizedClientManager(
client,
authorizedClient
);
authorizedClientManager.setAuthorizedClientProvider(authorizedProvider);
return authorizedManager;
}
#Bean
public ServletOAuth2AuthorizedClientExchangeFilterFunction oauthClient(OAuth2AuthorizedClientManager authorizedManager) {
return new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedManager);
}
}
#Service
#RequiredArgsConstructor
public class AuthClientManager {
public static final String SERVICE_ID = "my-service";
private final OAuth2AuthorizedClientManager oAuth2Manager;
private final ServletOAuth2AuthorizedClientExchangeFilterFunction
filterFunction;
private final WebClient webClient;
private WebClient client;
public WebClient getClient() {
return Optional.ofNullable(client)
.orElseGet(() -> {
OAuth2AuthorizeRequest authorizeRequest =
OAuth2AuthorizeRequest.withClientRegistrationId(SERVICE_ID)
.principal(SERVICE_ID)
.build();
client = webClient
.mutate()
.filter(
(request, next) -> next
.exchange(
ClientRequest.from(request)
.attributes(
oauth2AuthorizedClient(
oAuth2Manager.authorize(authorizeRequest)
)
).build()
)
)
.apply(filterFunction.oauth2Configuration())
.build();
return client;
});
}
}
endpoint
#RequestMapping("email")
public interface RestController {
#PreAuthorize("hasScope(T(......MESSAGE_SEND)")
#PostMapping("v1/message")
ResponseEntity<Void> send(#Valid #RequestBody Dto dto);
}
implementation of endpoint
#RestController
#RequiredArgsConstructor
#Slf4j
public class RestControllerImpl implements RestController {
#Override
public ResponseEntity<Void> send(Dto dto) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Slf4j
#RequiredArgsConstructor
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new ScopeAwareExpressionHandler();
}
#Bean
#Order(0)
SecurityFilterChain apiFilterChain(
HttpSecurity http,
#Value("${spring.security.oauth2.client.provider-uri}") String hostname
) throws Exception {
return http
.cors()
.configurationSource(request ->
new CorsConfiguration()
.applyPermitDefaultValues()
)
.and()
.csrf().disable()
.requestMatchers(
requestMatcherConfigurer -> requestMatcherConfigurer.antMatchers("/**")
)
.authorizeRequests(authorizeRequestsCustomized -> authorizeRequestsCustomized
.antMatchers(
"/swagger-ui/**"
)
.permitAll()
.anyRequest()
.authenticated()
)
.oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer ->
httpSecurityOAuth2ResourceServerConfigurer
.jwt()
.jwkSetUri(hostname + "/oauth2/jwks")
)
.build();
}
}
application.yaml
spring:
security:
oauth2:
client:
registration:
my-service: # my-service
provider: spring
client-id: 1
client-secret:1
authorization-grant-type: client_credentials
scope: message.send
client-name: 1
provider:
spring:
issuer-uri:locachost....
user-info-uri: locachost..../api/v1/users/me
user-name-attribute: id
A would like to write an integration test for this endpoint to verify that the Oauth2 client for Credentials flow is configured correctly. well, for one thing, the work of my endpoint.
How could I do that ?
I have not found any examples suitable for my task.
Could someone share knowledge about this case.

If you want to write integration test:
start authorization server
script query to get authorization token with WebClient or something
set test request Authorization header with bearer token you got.
I'd rather write unit tests with #WebmvcTest or #WebfluxTest bfluxTest and configure test security context with jwt() MockMvc post processor (or Word bTestClient mutator) from spring-security-test or #WithMockJwtAuth from https://github.com/ch4mpy/spring-addons

Related

Spring Cloud Gateway - Intercept under hood request/response to Keycloak IDP

We are implementing a Spring Cloud Gateway application (with Webflux) that is mediating the OAuth2 authentication with Keycloak.
SCG checks if the Spring Session is active: if not, redirects to Keycloak login page and handles the response from the IDP. This process is executed out-of-the-box by the framework itself.
Our needs is to intercept the IDP Keycloak response in order to retrieve a field from the response payload.
Do you have any advices that will help us to accomplish this behavior?
Thanks!
You can implement ServerAuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
private ServerRedirectStrategy redirectStrategy;
public AuthenticationSuccessHandler(AuthenticationService authenticationService) {
redirectStrategy = new DefaultServerRedirectStrategy();
}
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
if(authentication instanceof OAuth2AuthenticationToken) {
//Your logic here to retrieve oauth2 user info
}
ServerWebExchange exchange = webFilterExchange.getExchange();
URI location = URI.create(httpRequest.getURI().getHost());
return redirectStrategy.sendRedirect(exchange, location);
}
}
And update your security configuration to include success handler:
#Configuration
public class SecurityConfiguration {
private AuthenticationSuccessHandler authSuccessHandler;
public SecurityConfiguration(AuthenticationSuccessHandler authSuccessHandler) {
this.authSuccessHandler = authSuccessHandler;
}
#Bean
SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchange -> exchange
//other security configs
.anyExchange().authenticated()
.and()
.oauth2Login(oauth2 -> oauth2
.authenticationSuccessHandler(authSuccessHandler)
);
return http.build();
}
}

Spring Security Customizing Authorization Endpoint URL

I am implementing a Spring MVC REST web service and attempting to security it with Spring Security Oauth2.
My authorization URL is at the following address (note that there is no registration id):
http://localhost:8080/myoauthserver/oauthservlet
So, in my Spring Security Config, I have this:
#Bean
#Profile("dev")
public ClientRegistration devClientRegistration() {
// set up variables to pass to ClientRegistration
ClientRegistration result = ClientRegistration
.withRegistrationId("") // this obviously doesn't work
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri(authorizationUri)
.tokenUri(clientSecret)
.userInfoUri(userInfoUri)
.userNameAttributeName(userNameAttribute)
.redirectUri(redirectUrl)
.providerConfigurationMetadata(providerDetails)
.build();
return result;
}
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistrationRepository repository = new InMemoryClientRegistrationRepository(devClientRegistration());
return repository;
}
#Bean
#Profile("dev")
public SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
// this prevents throwing an exception in the oauth2 lambda function
Map<String, String> loginProps = ...
http
.authorizeRequests()
// what we want is for a few urls to be accessible to all, but most to require
// oauth authentication/authorization
.antMatchers("/login/**","/error/**", "/oauth2/**").anonymous()
.anyRequest().authenticated()
.and()
.oauth2Login(oauth2 -> {
oauth2
.clientRegistrationRepository(clientRegistrationRepository(loginPropsCopy))
.authorizationEndpoint()
.baseUri("http://localhost:8080/myoauthserver/oauthservlet");
}
);
return http.build();
}
How do I customize the entire authorization endpoint to not try to tack the registration id onto it?

automate the OAuth2 refresh_token process with SpringBoot 2

I have a SpringBoot2 application, a MainApp as a resource-server, KeyCloak as AuthorizationServer and a maven module, which is related to the MainApp, as a OAuth2LoginClient.
In other words, in MavenModule I have the follow SecurityConfig:
#Configuration
#PropertySource("classpath:idm.properties")
public class Auth0Provider extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.requestMatchers(PROTECTED_URLS).authenticated()
.anyRequest().authenticated()
)
.oauth2Login().redirectionEndpoint().baseUri("/callback*");
http.csrf().disable();
}
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/idmauth/**")
);
}
There is also a controller that intercepts the protected call:
#Value("${oauth.redirectURL}")
private String redirectURL;
#Autowired
private OAuth2AuthorizedClientService clientService;
#RequestMapping(method = RequestMethod.GET, path = "/redirect")
public RedirectView redirectWithUsingRedirectView(OAuth2AuthenticationToken oauthToken, RedirectAttributes attributes) {
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String token = client.getAccessToken().getTokenValue();
attributes.addAttribute("jwt", token);
return new RedirectView(redirectURL);
}
This return the AccessToken to my frontend. Clearly in my idm.properties file I have the spring.oauth2.client.provider and spring.oauth2.client.registration info.
Now the MainApp is a SpringBoot2 WebApp with this simple SecurityConfig:
#EnableWebSecurity
#Configuration
public class Oauth2RestApiSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
And in it's application.properties just the line:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://<host>/protocol/openid-connect/certs
All works fine but, when the token expire, the only way I have currently found to refresh my token
is to manually do this HTTP-POST:
POST /auth/realms/<audience>/protocol/openid-connect/token HTTP/1.1
Host: <host>
Content-Type: application/x-www-form-urlencoded
Content-Length: 844
client_id=<my_client_id>
&client_secret=<my_client_secret>
&refresh_token=<refresh_token_previously_obtained>
&grant_type=refresh_token
Is there a better way to do this? Maybe inside the SecurityConfig or with a specific path inside spring.oauth2.x properties?
Note that refreshing an access token is done on the OAuth 2.0 client side.
This is done automatically by Spring Security if you have configured a WebClient to be used when requesting protected resources.
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
When you have done so, the expired OAuth2AccessToken will be refreshed (or renewed) if an OAuth2AuthorizedClientProvider is available to perform the authorization.

Using more than one JWT Decoder with Spring Webflux Security

I read this post about using multiple JWT Decoders in Spring Security flow which seems easy, except that I'm using Spring Webflux and not Spring WebMVC , which has the convenient WebSecurityConfigurerAdapter that you can extend to add multiple AuthenticationProvider instances. With Webflux you no longer extend some class to configure security.
So what's the problem while trying to replicate this with Webflux? This . As you can read there Webflux doesn't use AuthenticationProvider , you have to declare a ReactiveAuthenticationManager instead. The problem is I don't know how to make Spring use multiple authentication managers, each of them using its own ReactiveJwtDecoder.
My first authentication manager would be the one spring creates automatically using this property:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${scacap.auth0.issuer}
And my second Authentication Manager would be a custom one I'm declaring in my Security #Configuration:
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
#EnableConfigurationProperties(JwkProperties::class)
internal class SecurityConfiguration {
#Bean
fun securityFilter(
http: ServerHttpSecurity,
scalableAuthenticationManager: JwtReactiveAuthenticationManager
): SecurityWebFilterChain {
http.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(Auth0AuthenticationConverter())
return http.build()
}
#Bean
fun customAuthenticationManager(jwkProperties: JwkProperties): JwtReactiveAuthenticationManager {
val decoder = NimbusReactiveJwtDecoder.withJwkSource { Flux.fromIterable(jwkProperties.jwkSet.keys) }.build()
return JwtReactiveAuthenticationManager(decoder).also {
it.setJwtAuthenticationConverter(ScalableAuthenticationConverter())
}
}
}
I am debugging and it seems only one authentication manager is being picked so only auth0 tokens can be validated, but I also want to validate tokens with my own JWKS
Okay, so this is what I ended up doing:
Instead of trying someway to pass several AuthenticationManagers to Spring Security flow, I created one wrapper which I call DualAuthenticationManager. This way for Spring there is only one manager and I do the orchestration inside my wrapper like firstManager.authenticate(auth).onErrorResume { secondManager.authenticate(auth) }.
It ended up being shorter than I thought it would be. It's all in a #Bean function in my security #Configuration . And each manager has it's own converter function so I can create my UserToken model with two different JWTs :)
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
#EnableConfigurationProperties(*[JwtProperties::class, Auth0Properties::class])
internal class SecurityConfiguration(
private val jwtProperties: JwtProperties,
private val auth0Properties: Auth0Properties
) {
#Bean
fun securityFilter(
http: ServerHttpSecurity,
dualAuthManager: ReactiveAuthenticationManager
): SecurityWebFilterChain {
http.csrf().disable()
.authorizeExchange()
.pathMatchers("/actuator/**").permitAll()
.pathMatchers("/user/**").hasAuthority(Authorities.USER)
.anyExchange().authenticated()
.and()
.oauth2ResourceServer().jwt()
.authenticationManager(dualAuthManager)
return http.build()
}
#Bean
fun dualAuthManager(): ReactiveAuthenticationManager {
val firstManager = fromOidcIssuerLocation(auth0Properties.issuer).let { decoder ->
JwtReactiveAuthenticationManager(decoder).also {
it.setJwtAuthenticationConverter(FirstAuthenticationConverter())
}
}
val secondManager = withJwkSource { fromIterable(jwtProperties.jwkSet.keys) }.build().let { decoder ->
JwtReactiveAuthenticationManager(decoder).also {
it.setJwtAuthenticationConverter(SecondAuthenticationConverter())
}
}
return ReactiveAuthenticationManager { auth ->
firstManager.authenticate(auth).onErrorResume { secondManager.authenticate(auth) }
}
}
}
This is how my converters look:
class FirstAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken> {
val authorities = jwt.getClaimAsStringList(AUTHORITIES) ?: emptyList()
val userId = jwt.getClaimAsString(PERSON_ID)
val email = jwt.getClaimAsString(EMAIL)
return Mono.just(
UsernamePasswordAuthenticationToken(
UserToken(jwt.tokenValue, UserTokenType.FIRST, userId, email),
null,
authorities.map { SimpleGrantedAuthority(it) }
)
)
}
}
Then in my controller I get the object I built in the converter by doing:
#AuthenticationPrincipal userToken: UserToken

Storing JWT tokens on OAuth2 web client using Spring Security

I'm implementing an OAuth2 web application Client using Spring Boot 2.1.3 and Spring Security 5.1.3 that is obtaining JWT tokens from an authorization server through authorization code grant type and calls a protected resource server.
This is how the implementation looks up till now:
Security configuration and a restTemplate bean used to call the protected resource:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.and().logout().logoutSuccessUrl("/");
}
#Bean
public RestTemplate restTemplate(OAuth2AuthorizedClientService clientService) {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new AuthorizationHeaderInterceptor(clientService));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
The interceptor that adds the authorization header (from the framework's InMemoryOAuth2AuthorizedClientService) in the restTemplate:
public class AuthorizationHeaderInterceptor implements ClientHttpRequestInterceptor {
private OAuth2AuthorizedClientService clientService;
public AuthorizationHeaderInterceptor(OAuth2AuthorizedClientService clientService) {
this.clientService = clientService;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String accessToken = null;
if (authentication != null && authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) authentication;
String clientRegistrationId = auth.getAuthorizedClientRegistrationId();
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(clientRegistrationId, auth.getName());
accessToken = client.getAccessToken().getTokenValue();
request.getHeaders().add("Authorization", "Bearer " + accessToken);
}
return execution.execute(request, bytes);
}
}
And the controller that calls the protected resource server:
#Controller
#RequestMapping("/profile")
public class ProfileController {
#Autowired
private RestTemplate restTemplate;
#Value("${oauth.resourceServerBase}")
private String resourceServerBase;
#GetMapping
public String getProfile(Model model) {
Profile profile = restTemplate.getForEntity(resourceServerBase + "/api/profile/", Profile.class).getBody();
model.addAttribute("profile", profile);
return "profile";
}
}
The OAuth2 client configuration is directly in the application.yml:
spring:
security:
oauth2:
client:
registration:
auth-server:
client-id: webClient
client-secret: clientSecret
scope: read,write
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8081/client/login/oauth2/code/auth-server
provider:
auth-server:
authorization-uri: http://localhost:8080/auth-server/oauth/authorize
token-uri: http://localhost:8080/auth-server/oauth/token
user-info-uri: http://localhost:8082/resource-server/users/info
user-name-attribute: user_name
After doing some debugging I've observed that at the end of a successful authentication flow through OAuth2LoginAuthtenticationFilter the framework is storing the obtained access and refresh JWT tokens under OAuth2AuthorizedClient model in memory through the provided InMemoryOAuth2AuthorizedClientService.
I am trying to find out how to override this behaviour so that the tokens can remain available after a server restart. And also keep the user logged in based on this.
Should I just provide a custom OAuth2AuthorizedClientService implementation? How could I configure Spring Security to use it? And should this custom implementation store the tokens in a cookie?
Should I just provide a custom OAuth2AuthorizedClientService
implementation?
I think yes, for solving your use case
How could I configure Spring Security to use it?
From spring doc:
If you would like to provide a custom implementation of
AuthorizationRequestRepository that stores the attributes of
OAuth2AuthorizationRequest in a Cookie, you may configure it as shown
in the following example:
#EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Client()
.authorizationCodeGrant()
.authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
...
}
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
return new HttpCookieOAuth2AuthorizationRequestRepository();
}
}

Resources