Missing Oauth2 classes after update to Spring Boot 3 - spring

After updating a gateway service to Spring Boot 3.x (from Spring Boot 2.5.2), I discovered that the DefaultAccessTokenConverter, OAuth2Authentication, OAuth2AuthenticationManager, and RemoteTokenServices are removed or otherwise moved to a different library.
This is what the build.gradle dependencies were before the update:
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-security'
implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
implementation 'org.springframework.security:spring-security-oauth2-jose'
implementation 'org.springframework.security:spring-security-config'
implementation 'org.springframework.cloud:spring-cloud-starter-oauth2:2.2.5.RELEASE'
And after:
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.0.0'
As far as I can tell, I have the correct libraries for OAuth2 in Spring Security 6.x/Spring Boot 3.x, and I see no mention of the above classes in the Spring Security 6.x migration guide.
There was also an answered question about migrating from OAuth2 to Spring Security 5. This may be relevant in my case, but I don't have enough experience with authentication services to be sure.
The aforementioned classes are used extensively throughout my gateway service and I'm not sure how to replace them.
One such example of OAuth2Authentication usage:
#Override
public OAuth2Authentication extractAuthentication(final Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
authentication.getOAuth2Request().getExtensions().computeIfAbsent(GIVEN_NAME,
v -> map.get(GIVEN_NAME) != null ? map.get(GIVEN_NAME).toString() : "");
authentication.getOAuth2Request().getExtensions().computeIfAbsent(PREFERRED_NAME,
v -> map.get(PREFERRED_NAME) != null ? map.get(PREFERRED_NAME).toString() : "");
authentication.getOAuth2Request().getExtensions().computeIfAbsent(CLIENT_NAME,
v -> map.get(CLIENT_NAME) != null ? map.get(CLIENT_NAME).toString() : "");
authentication.getOAuth2Request().getExtensions().computeIfAbsent(FEATURES,
v -> map.get(FEATURES) != null ? String.join(DELIMITER,
(List<String>) map.get(FEATURES)) : "");
authentication.getOAuth2Request().getExtensions().computeIfAbsent(PARTITION_ROLES,
v -> map.get(PARTITION_ROLES) != null ? String.join(DELIMITER,
(List<String>) map.get(PARTITION_ROLES)) : "");
return authentication;
}
Any help would be great, thanks!

Depending on your resource-server using a JWT decoder or access-token introspection (opaqueToken), you can customize the authentication converter with either:
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
http.oauth2ResourceServer().opaqueToken().authenticationConverter(introspectionAuthenticationConverter)
Default Authentication implementations are respectively JwtAuthenticationToken token and BearerTokenAuthentication for respectively JWT decoding and introspection, but you may return any type of Authentication you like.

Related

Validate the api with user defined roles in spring boot oauth resource server

I am currently working on resource server implemented by the spring boot o auth 2.0. Spring boot version would be 3.0.0. Need to authrorize the api with user-authorities. I tried below samples.
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/opaque-token.html. But did n't get the user-authorities attribute. Can anyone sugget me the approach to to validate api with user roles in #PreAuthorize anotations.
Sample token format :
{
"sub": "admin#support.com",
"aud": "client",
"user-authorities": [
"READ_PRIVILEGE",
"WRITE_PRIVILEGE"
],
"azp": "client",
"iss": "http://localhost:8080",
"exp": 1676210945,
"iat": 1676209145
}
Two options to override default authorities mapping in case of token introspection (read user-authorities claim in your case instead of scope).
override the all OpaqueTokenIntrospector, as explained in the doc you linked
override just the OpaqueTokenAuthenticationConverter to which the default introspector delegates the conversion from successful introspection result to an Authentication instance
The second option is less intrusive as you don't take responsibility of how introspection is made (which REST client is used, how client authentication is performed, query built, response parsed, exceptions handled, etc.).
Sample usage:
#Bean
OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter() {
return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> {
// double check the claim name to extract authorities from and if a prefix should be added:
// in the sample provided in your question, it is "user-authorities" claim
// but in the sample linked in your comment, it is "scope" claim and "SCOPE_" prefix should be added for "hasAuthority("SCOPE_openid")" to be matched
#SuppressWarnings("unchecked")
final var rolesClaim = Optional.ofNullable((List<String>) authenticatedPrincipal.getAttribute("user-authorities")).orElse(List.of());
return new BearerTokenAuthentication(
authenticatedPrincipal,
new OAuth2AccessToken(
TokenType.BEARER,
introspectedToken,
authenticatedPrincipal.getAttribute(IdTokenClaimNames.IAT),
authenticatedPrincipal.getAttribute(IdTokenClaimNames.EXP)),
// You should insert a ".map()" step to insert a prefix if access-control rules expect one (that is not visible in the access token)
rolesClaim.stream().map(SimpleGrantedAuthority::new).toList());
};
}
#Bean
SecurityFilterChain securityFilterChain(
HttpSecurity http,
OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter)
throws Exception {
http.oauth2ResourceServer().opaqueToken().authenticationConverter(introspectionAuthenticationConverter);
...
return http.build();
}
Complete sample there with one of "my" starters which make things much simpler (check security conf and properties file against what you have in yours).

Duplicate config code problem Spring Boot microservices

Is there a way to create a spring configuration class and use it to all my microservices?
For example, I have to copy the following configuration class and paste it through all my microservices which means that when I want to make a small change I have to go through all the microservices editing the same class.
I have investigated and the most I could find is to create a module with all the classes in common and import it in my microservices by the pom, what happens with this is that when I want to get the SecurityContextHolder.getContext() this is null for a context issue and I do not know very well how to give solution to this or what other alternatives there are.
#Configuration
public class FeignGlobalConfiguration {
#Bean
public ErrorDecoder errorDecoder() {
return new RetrieveMessageErrorDecoder();
}
#Bean
public RequestInterceptor requestInterceptor(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return requestTemplate -> {
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}
}
The problem is your bean definition.
The line Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); is called when the bean is constructed so only once. After that the reference is used (which is probably null at the time of construction.
To solve move tha tline inside the lambda so it gets evaluated each time a request is processed.
#Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}

Keycloak refuses to return roles on request

My Keycloak is returning an OAuth2AuthenticationToken, but refuses to add the roles the user has. Instead, it's returning the somewhat generic:
Authority: ROLE_USER
Authority: SCOPE_email
Authority: SCOPE_openid
Authority: SCOPE_profile
The Java method is
#GetMapping
public String work_queue(Principal principal, Model model) {
...
//Principal comes in as org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken
//
Object principal2 = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// principal2 is org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
}
In Postman, I was able to convince Keycloak to return a JWT token using the Get Token functionality. Inside the JWT, after decompiling, I saw all the roles I wanted it to see. Yet somehow, the Spring Boot configuration decided to shorten this down to something much smaller.
What would someone need to know to guess a good solution?
Please see Spring Boot not fetching Keycloak Roles
You need to provide a #Bean implementing Converter<Jwt, Collection<GrantedAuthority>> to override Spring's JwtGrantedAuthoritiesConverter (it is this bean mapping scopes to authorities).
But, you could use a lib I worte for spring-boot OpenID resource-servers auto-configuration (works with any OIDC authorization-server, Keycloak included) which might save you a lot of configuration and provide you with a more OpenID oriented Authentication than JwtAuthenticationToken: OAuthentication<OpenidClaimSet>, which exposes OpenidClaimSet as principal.
It's available from maven-central and source is there: https://github.com/ch4mpy/spring-addons.
This very simple tutorial should be enough (you can refer to this other one for more advanced use-cases):
spring-boot app with those dependencies:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-webmvc-addons</artifactId>
<version>4.4.7</version>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>4.4.7</version>
<scope>test</scope>
</dependency>
that java config
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class WebSecurityConfig {
}
those properties
# shoud be set to where your authorization-server is
com.c4-soft.springaddons.security.token-issuers[0].location=https://localhost:9443/auth/realms/master
# shoud be configured with a list of private-claims this authorization-server puts user roles into
# below is default Keycloak conf for a `spring-addons` client with client roles mapper enabled
com.c4-soft.springaddons.security.token-issuers[0].authorities.claims=realm_access.roles,resource_access.spring-addons.roles
# advanced CORS configuration can be made per API route
com.c4-soft.springaddons.security.cors[0].path=/greet/**
com.c4-soft.springaddons.security.cors[0].allowed-origins=https://localhost,https://localhost:8100,https://localhost:4200
# use IDE auto-completion or see SpringAddonsSecurityProperties javadoc for complete configuration properties list to change defaults:
# - anonymous enabled
# - empty list of permitAll() routes
# - CSRF disabled
# - stateless session management
# - case and prefix for mapped authorities
# - 401 (unauthorized) instead of 302 (redirect to login)
# - CORS allowed methods, headers, etc. for each path
Yes, with 2 dependencies, 1 configuration line and 4 properties, we just configured an OpenID resource-server with CORS and authorities mapping from random private claims (plus a few other things useful to resource servers). Could it be simpler?
As an extra bonus, it comes with annotations to configure your unit-tests security context (this is from the third dependency):
#WebMvcTest(GreetingController.class)
#AutoConfigureSecurityAddons
#Import(WebSecurityConfig.class)
class GreetingControllerTest {
#Autowired
MockMvc mockMvc;
#Test
#OpenId(authorities = { "NICE_GUY", "AUTHOR" }, claims = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenGrantedWithNiceGuyThenCanGreet() throws Exception {
mockMvc
.perform(get("/greet").secure(true))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
#Test
#OpenId(authorities = { "AUTHOR" }, claims = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenNotGrantedWithNiceGuyThenForbidden() throws Exception {
mockMvc.perform(get("/greet").secure(true)).andExpect(status().isForbidden());
}
}
P.S.
Please give a star to https://github.com/ch4mpy/spring-addons if you find it useful.

SessionLocaleResolver equivalent in Spring WebFlux - resolve locale through session data

If we need to resolve the locale value from session data, how can we implement this in a reactive way in Spring WebFlux?
For non-reactive web application, SessionLocaleResolver is available to resolve the locale from session data.
For Spring WebFlux, the way to intercept the locale resolver is using LocaleContextResolver. The default implementation provided in Spring Boot WebFlux starter is AcceptHeaderLocaleContextResolver. The problem with this interface is that it only provides sync method resolveLocaleContext(ServerWebExchange). If I need to resolve the locale from request query parameters or the Accept-Language headers, it works perfectly, because ServerWebExchange provides direct methods to access the request object, which in turn provides methods to access parameters and headers directly.
override fun resolveLocaleContext(exchange: ServerWebExchange): LocaleContext {
val langParams = exchange.request.queryParams["lang"]
var targetLocale: Locale? = null
if (langParams != null && langParams.isNotEmpty()) {
for (lang in langParams) {
val locale = Locale.forLanguageTag(lang)
if (locale != null) {
targetLocale = locale
break
}
}
}
return SimpleLocaleContext(targetLocale ?: Locale.forLanguageTag("en-US"))
}
However, if I need to resolve the locale from session data, there is a problem. ServerWebExchange#getSession() returns Mono<WebSession>. I am not able to get session data in a sync method unless I call Mono#block(). However, this is not an ideal, reactive way.
Is there any way to resolve this? Or are there any plan in Spring to change this?
(There was a similar question previously which got buried: Spring WebFlux - SessionLocaleResolver)

OAuth2 | ClientCredentialsResourceDetails | deprecated

I am new to spring security, and i come across to implement OAuth2 with client_credentials as Grant type.
i am using below piece of code, but i am getting suggestion that ClientCredentialsResourceDetails, OAuth2RestTemplate & OAuth2AccessToken are deprecated.
Can someone help with the alternate to this ?
private String getAuthTocken(){
final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setClientId("ceapiClientId");
resourceDetails.setClientSecret("ceapiClientSecret");
resourceDetails.setGrantType("client_credentials");
resourceDetails.setAccessTokenUri("https://auth.abcdcommerce.com/oauth-server/oauth/token");
final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails);
final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken();
final String accessTokenAsString = accessToken.getValue();
return accessTokenAsString;
}
The alternative is to use the new non-blocking WebClient or a RestTemplate with an interceptor over the deprecated OAuthRestTemplate. Everything in the spring-security-oauth artifacts has an end of life road map.
https://spring.io/blog/2019/11/14/spring-security-oauth-2-0-roadmap-update
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix
The migration guide can be found here,
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
From the migration guide,
Spring Security chooses to favor composition and instead exposes an OAuth2AuthorizedClientService, which is useful for creating RestTemplateinterceptors or WebClient exchange filter functions. Spring Security provides ExchangeFilterFunction s for both Servlet- and WebFlux-based applications that both leverage this service.
There is a migration example available here,
https://github.com/jgrandja/spring-security-oauth-5-2-migrate

Resources