I am upgrading an app from Spring Boot 2.x to 3.
The app has OAuth2 implemented and is configured as a Resource Server.
When converting the security configuration to how it's defined here, I need a custom Introspector to override SB's own autoconfigured one. Still, it looks like my configuration is not picked up and the tests keep failing with exception
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'opaqueTokenIntrospector' defined in class path resource
[org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerOpaqueTokenConfiguration$OpaqueTokenIntrospectionClientConfiguration.class]:
Failed to instantiate
[org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector]:
Factory method 'opaqueTokenIntrospector' threw exception with message:
clientId cannot be null
@ConditionalOnMissingBean(OpaqueTokenIntrospector.class)
annotation should only work when there is no other bean of type OpaqueTokenIntrospector
, and @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.opaquetoken.introspection-uri")
annotation is placed on the class SpringOpaqueTokenIntrospector
.
CustomIntrospector I want configured.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@Profile("!local")
public class SecurityConfiguration {
private SecurityHandler securityHandler;
@Autowired
public SecurityConfiguration(final SecurityHandler securityHandler) {
this.securityHandler = securityHandler;
}
@Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
http
.exceptionHandling(exception ->
exception.accessDeniedHandler(securityHandler)
.authenticationEntryPoint(securityHandler))
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.NEVER))
.httpBasic(AbstractHttpConfigurer::disable)
.oauth2ResourceServer(server ->
server.opaqueToken(Customizer.withDefaults()))
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/jolokia/**").permitAll());
return http.build();
}
@Bean
public OpaqueTokenIntrospector introspector(final OAuth2ResourceServerProperties resourceServerProperties, final RestTemplateBuilder rtBuilder) {
final String introspectionUri = resourceServerProperties.getOpaquetoken().getIntrospectionUri();
return new CustomOpaqueTokenIntrospector(introspectionUri, rtBuilder.build());
}
}
application.yaml
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri: https://idp.example.com/introspect
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ateebahmed</groupId>
<artifactId>my-secure-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.13</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--Spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- Jolokia -->
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-support-spring</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-jmx</artifactId>
</dependency>
<!-- Oauth2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<!-- TESTING -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<scope>test</scope>
</dependency>
<!-- TEST CONTAINERS -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.16.0</version>
</dependency>
<!--COMMON LIBRARIES-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
When I tried to run the app with client-id
and client-secret
as dummy values in application.yaml
, it ran fine.
I tried other ways of overriding introspector as mentioned in the docs like using the introspector
method, but it doesn't affect as well.
How do I make sure SB's own classes are not picked up.
If you think this is an issue with SB's autoconfiguration itself, let me know I'll create an issue there.