Home > Net >  SpringOpaqueTokenIntrospector Class Not Found
SpringOpaqueTokenIntrospector Class Not Found

Time:11-10

I've been facing some issues while running Spring Boot 2.7.0 and spring-boot-starter-oauth2-resource-server. My goal is to use Keycloak to check tokens from http requests. But since I've added spring-boot-starter-oauth2-resource-server to my application it fails to start because of exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerOpaqueTokenConfiguration$OpaqueTokenIntrospectionClientConfiguration': Lookup method resolution failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerOpaqueTokenConfiguration$OpaqueTokenIntrospectionClientConfiguration] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@6f94fa3e]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:289)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1302)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1219)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
    at com.test.TestApplication.main(TestApplication.java:14)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerOpaqueTokenConfiguration$OpaqueTokenIntrospectionClientConfiguration] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@6f94fa3e]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:485)
    at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:267)
    ... 23 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector
    at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
    at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3402)
    at java.base/java.lang.Class.getDeclaredMethods(Class.java:2504)
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:467)
    ... 25 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 29 common frames omitted


Process finished with exit code 0

Dependencies of project:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>

Do you have any idea what is wrong with this setup? Thanks in advance

CodePudding user response:

First, if your authorization-server produces JWTs (Keycloak does), you'd probably better use JWT decoder than token introspection. This is much more efficient: all JWTs are validated with a public key downloaded only once when introspection requires resource-server (Spring REST API) to submit each and every access-token to authorization-server (Keycloak).

Second, double check the spring-security-oauth2-resource-server version in your dependency tree: SpringOpaqueTokenIntrospector is there from 5.6 only.

Third, I contributed an easier way to map authorities for token introspection in version 5.8: provide an authentication converter (handle the introspection result) instead of overloading the all introspector:

http.oauth2ResourceServer().opaqueToken().authenticationConverter(authenticationConverter);

where authenticationConverter can be implemented as:

(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> new BearerTokenAuthentication(
            authenticatedPrincipal,
            new OAuth2AccessToken(
                    OAuth2AccessToken.TokenType.BEARER,
                    introspectedToken,
                    authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT),
                    authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP)),
            authoritiesConverter.convert(authenticatedPrincipal.getAttributes()));

and authoritiesConverter is a Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> (maps authorities from Keycloak introspection JSON response, which you should have already implemented)

Last, you might find useful tips for configuring your resource-server in this tutorials: it could be as simple as

        <dependency>
            <groupId>com.c4-soft.springaddons</groupId>
            <!-- change "webmvc" with "webflux" for reactive apps -->
            <!-- change "jwt" with "introspecting" for access-token introspection instead of JWT decoding -->
            <artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
            <!-- 6.0.4 goes with spring-boot 3.0.0-RC1, use 5.x with boot 2.6.x and below -->
            <version>6.0.4</version>
        </dependency>
@EnableMethodSecurity
@Configuration
public class SecurityConfig { }
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://localhost:8443/realms/master/protocol/openid-connect/token/introspect
spring.security.oauth2.resourceserver.opaquetoken.client-id=spring-addons-confidential
spring.security.oauth2.resourceserver.opaquetoken.client-secret=change-me

com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/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.issuers[0].authorities.claims=realm_access.roles,resource_access.spring-addons-public.roles,resource_access.spring-addons-confidential.roles

# Fine-grained CORS configuration can be set per path as follow:
com.c4-soft.springaddons.security.cors[0].path=/sample/**

# More useful resource-server security conf from preperties, use IDE auto-completion
  • Related