How to validate OAuth token in Spring Cloud Gateway - spring-boot

I have a Spring Cloud gateway application which currently supports "HTTP basic auth". I'm planning to use OAuth 2.0 with Keycloak as the provider. I have started by adding required packages (listed below).
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
I'm new to this and want to support the following things.
Register clients dynamically to Keycloak
Validate the current request token with Keycloak to allow/ deny the request
My SecurityConfig.java is as given below.
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.csrf().disable()
.formLogin().disable()
.logout().disable()
.authorizeExchange(exchanges -> exchanges.anyExchange().permitAll())
.oauth2Client();
return http.build();
}
#Bean
public WebClientHttpRoutingFilter webClientHttpRoutingFilter(
#Qualifier("webClient") WebClient webClient,
ObjectProvider<List<HttpHeadersFilter>> headerFilters) {
return new WebClientHttpRoutingFilter(webClient, headerFilters);
}
#Bean
public WebClient webClient(
#Qualifier("reactiveOAuth2AuthorizedClientManager")
ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(reactiveOAuth2AuthorizedClientManager);
oauth.setDefaultOAuth2AuthorizedClient(true);
oauth.setDefaultClientRegistrationId("keycloak");
return WebClient.builder()
.filter(oauth)
.build();
}
#Bean
public ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}

Related

Securing Spring Cloud Gateway with Spring Security

I am struggling with configuring security for my Spring Cloud Gateway service.
For now i have configured in my api-gateway just one route to user service /api/v1/users. Requests are correctly routed to user service untill I add Spring Security to the dependescies.
Even with that simple config, that should allow all traffic, I am still getting 401 Unathorized response:
#Configuration
#EnableWebFluxSecurity
class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity serverHttpSecurity) {
return serverHttpSecurity
.authorizeExchange()
.anyExchange().permitAll().and()
.csrf().disable()
.build();
}
}
What am I doing wrong?
You need to create user to do that. See the sample attached in below. I am using in-memory user to authenticate. Note in-memory user is just for testing purpose only.
#Configuration
public class InMemoryUserSecurityAdapter {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/school-library-service/**").authenticated()
.and().authenticationManager(reactiveAuthenticationManager())
.authorizeExchange().anyExchange().permitAll().and()
.httpBasic().and()
.build();
}
#Bean
ReactiveAuthenticationManager reactiveAuthenticationManager(){
return new UserDetailsRepositoryReactiveAuthenticationManager(getInMemoryUserDetails());
}
#Bean
public MapReactiveUserDetailsService getInMemoryUserDetails() {
UserDetails admin = User.withDefaultPasswordEncoder().username("admin1").password("password")
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(admin);
}
}
https://github.com/DeepuGeorgeJacob/school-management/blob/main/security/in-memory-user-security/src/main/java/com/school/management/config/InMemoryUserSecurityAdapter.java
Happy coding :)

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?

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

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

customize state parameter with Oauth2client using spring security

I want to customize state parameter with Oauth2client using spring security OIDC. I am using spring reactive. The issue is I am not able to add customize state in client registration. Below is my security configuration.
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/","/jwt").permitAll()
.anyExchange().authenticated().and()
.oauth2Client()
.build();
}
#Bean
public ReactiveClientRegistrationRepository reactiveClientRegistrationRepository(RegisteredClients clients) {
List<ClientRegistration> clientRegistrations = clients.getClients()
.entrySet().stream()
.map(clientRegistration -> {
String registrationId = clientRegistration.getKey();
RegisteredClients.OAuthClient client = clientRegistration.getValue();
return ClientRegistration.withRegistrationId(registrationId)
.clientId(client.getClientId())
.clientSecret(client.getClientSecret())
.redirectUriTemplate(client.getRedirectUri())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.tokenUri(client.getTokenUri())
.authorizationUri(client.getAuthorizationUri())
.scope("openid")
.build();
})
.collect(toList());
return new InMemoryReactiveClientRegistrationRepository(clientRegistrations);
}
To customize the authorization request, you should wire an ServerAuthorizationRequestResolver:
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
// ...
.oauth2Client((oauth2) -> oauth2
.authorizationEndpoint((authorization) -> authorization
.authorizationRequestResolver(authorizationRequestResolver)
)
)
.build();
}
#Bean
OAuth2ServerAuthorizationRequestResolver authorizationRequestResolver
(ReactiveClientRegistrationRepository registrations) {
DefaultServerOAuth2AuthorizationRequestResolver resolver =
new DefaultServerOAuth2AuthorizationRequestResolver(registrations);
resolver.setAuthorizationRequestCustomizer((request) -> request.state(...));
return resolver;
}
Spring Security has some related servlet documentation that may be useful as well.

How can I use client_credentials to access another oauth2 resource from a resource server?

I want to use client_credentials to access another oauth2-protected resource from a reactive resource-server. The part where I'm accessing the resource server using an issued token is working, but not calling the other resource using webclient.
Using UnAuthenticatedServerOAuth2AuthorizedClientRepository I get serverWebExchange must be null, and using AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository I get principalName must be null.
Using https://www.baeldung.com/spring-webclient-oauth2 works as long as I call the client as a CommandLineRunner. None of the other suggestions I have found here on stackoverflow has worked.
What am I missing here? I am using Spring Security 5.2.0 and Spring Boot 2.2.0.
ClientConfig:
#Configuration
public class ClientSecurityConfig {
// UnAuthenticatedServerOAuth2AuthorizedClientRepository version
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder()
.filter(oauth)
.build();
}
#Bean
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider(CustomClientConfig clientConfig) {
return ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(clientCredentialsGrantBuilder ->
clientCredentialsGrantBuilder.accessTokenResponseClient(new CustomClient(clientConfig))) // Used to send extra parameters to adfs server
.build();
}
// AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository version
#Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth)
.build();
}
}
#Bean
ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository, CustomClientConfig clientConfig) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(clientCredentialsGrantBuilder ->
clientCredentialsGrantBuilder.accessTokenResponseClient(new CustomClient(clientConfig))) // Used to send extra parameters to adfs server
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
ResourceServerConfig:
#EnableWebFluxSecurity
class ResourceServerConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges ->
exchanges
.pathMatchers("/actuators/**", "/api/v1").permitAll()
.pathMatchers("/api/v1/**").hasAuthority("SCOPE_read")
.anyExchange().authenticated()
)
.formLogin().disable()
.httpBasic().disable()
.oauth2Client(withDefaults())
.oauth2ResourceServer().jwt();
return http.build();
}
#RestController()
#RequestMapping("/api/v1")
static class Ctrl {
final static Logger logger = LoggerFactory.getLogger(Ctrl.class);
final WebClient webClient;
public Ctrl(WebClient webClient) {
this.webClient = webClient;
}
#RequestMapping("protected")
Mono<JsonNode> protected(#RequestParam String data) {
return webClient.post()
.uri("https://other-oauth2-protected-resource")
.attributes(clientRegistrationId("myclient"))
.bodyValue("{\"data\": \"" + data + "\"}")
.retrieve()
.bodyToMono(JsonNode.class);
}
}
}
application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://adfsserver.com/adfs/services/trust
jwk-set-uri: https://adfsserver.com/adfs/discovery/keys
client:
registration:
myclient:
provider: adfs
client-id: <client-id>
client-secret: <client-secret>
authorization-grant-type: client_credentials
scope: read
provider:
adfs:
token-uri: https://adfsserver.com/adfs/oauth2/token
jwk-set-uri: https://adfsserver.com/adfs/discovery/keys
This has recently been fixed by the Spring Project Contributors as part of this PR but unfortunately the official Spring doc is not yet updated.
The normal servlet approach doc is here
If you prefer to choose the "reactive" approach, then configuring a webclient requires only two beans:
the AuthorizedClientManager Bean, and
the webClient Bean
#Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
#Bean
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder().filter(oauth).build();
}
You can refer to my Github Gist which has all the required configuration.

Resources