Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ClassNotFoundException: org.springframework.security.oauth2.server.resource.web.BearerTokenResolver

I am getting related error during validation using Spring Security oauth2 vs Keycloak. I don't think it's missing from my dependencies.

Error

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterChain' defined in class path resource [com/framework/security/SecurityConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/security/oauth2/server/resource/web/BearerTokenResolver
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-5.3.23.jar:5.3.23]
        at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.23.jar:5.3.23]
        at 
    Caused by: java.lang.ClassNotFoundException: org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
        at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[?:?]

My Security class

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/login/**").hasRole("role-name").anyRequest().authenticated().and().oauth2ResourceServer().jwt();
        return http.build();
    }
}

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
    </dependencies>

application.yml

security:
    oauth2:
      resource-server:
        jwt:
          issuer-uri: http://keycloak-url/realms/realm-id
          jwk-set-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/certs
      client:
        registration:
          client-ui:
            provider: keycloak
            client-id: client-ui
            client-secret: abcdefgh
            authorization-grant-type: authorization_code
            scope: openid
        provider:
          client-ui:
            authorization-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/auth
            token-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/token
            user-info-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/userinfo
            jwk-set-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/certs
            user-name-attribute: preferred_username

I tried to make many edits but I couldn't find the problem. How can i solve it.

like image 322
kernel Avatar asked May 25 '26 17:05

kernel


1 Answers

First, you should decide if your app is primarily an OAuth2 client with login or an OAuth2 resource server. Here you are mixing client with login (registration with authorization_code flow) and resource server configuration in the same security filter chain. This is something you should not be doing. Security mechanisms are too different:

  • oauth2Login (authorization_code flow) relies on sessions, requires CSRF protection and you usually expect 302 redirect to login to unauthorized requests
  • oauth2ResourceServer relies on access tokens, can be stateless, without CSRF protection and should respond with 401 unauthorized

oauth2resourceServer (REST API)

Complete tutorial.

Resource servers don't care about login nor OAuth2 flows which are client concerns. It only cares if a request is authorized (has a valid access token), if this token should be introspected or decoded, and if it should grant access to the requested resources based on the token claims.

Use Postman or any OAuth2 client which can authorize its requests and send any type of request (not just GET, but also POST, PUT and DELETE) to try your running API.

<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
@SpringBootApplication
public class ResourceServerMinimalApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceServerMinimalApplication.class, args);
    }

    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Bean
    SecurityFilterChain resourceServerSecurityFilterChain(
            HttpSecurity http,
            @Value("${resource-server.cors.allowed-origins:}#{T(java.util.Collections).emptyList()}") List<String> allowedOrigins)
            throws Exception {
        http.oauth2ResourceServer(oauth2 -> oauth2.jwt());
        http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        http.csrf(csrf -> csrf.disable());
        http.exceptionHandling(handeling -> handeling.authenticationEntryPoint((request, response, authException) -> {
            response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        }));
        http.authorizeHttpRequests().anyRequest().authenticated();
        http.cors(cors -> {
            if (allowedOrigins.isEmpty()) {
                cors.disable();
            } else {
                cors.configurationSource(corsConfig(allowedOrigins));
            }
        });
        return http.build();
    }

    CorsConfigurationSource corsConfig(List<String> allowedOrigins) {
        final var source = new UrlBasedCorsConfigurationSource();

        final var configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(allowedOrigins);
        configuration.setAllowedMethods(List.of("*"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setExposedHeaders(List.of("*"));

        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @RestController
    @RequestMapping("/api/v1")
    public static class GreetingController {
        @GetMapping("/greet")
        public ResponseEntity<GreetingDto> getGreeting(Authentication auth) {
            return ResponseEntity.ok(new GreetingDto("Hello %s!".formatted(auth.getName())));
        }

        static record GreetingDto(String message) {
        }
    }
}
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://keycloak-url/realms/realm-id

resource-server:
  cors:
    allowed-origins:
    - http://localhost:4200

oauth2Login

Client using authorization_code flow (gateway configured with TokenRelay filter or app with server-side rendered templates like Thymeleaf) Complete tutorial.

<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
@SpringBootApplication
public class ClientMinimalApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClientMinimalApplication.class, args);
    }

    @Order(Ordered.HIGHEST_PRECEDENCE + 1)
    @Bean
    SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {
        http.oauth2Login();
        final var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
        http.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));
        http.cors().disable();
        // sessions are required (enabled by default)
        // CSRF protection is required (enabled by default) because security is based on sessions
        http.authorizeHttpRequests().requestMatchers("/login/**", "/oauth2/**").permitAll() // this is required for unauthenticated users to login
                .anyRequest().authenticated();
        return http.build();
    }

    @Controller
    @RequestMapping
    public static class UiController {
        @GetMapping("/")
        public RedirectView getIndex() throws URISyntaxException {
            return new RedirectView("/ui/greet");
        }

        @GetMapping("/ui/greet")
        public String getGreeting(Authentication auth, Model model) throws URISyntaxException {
            model.addAttribute("message", "Hello %s!".formatted(auth.getName()));
            return "greet";
        }
    }
}
spring:
  security:
    oauth2:
      client:
        provider:
          keycloak-realm-id:
            issuer-uri: http://keycloak-url/realms/realm-id
        registration:
          keycloak-confidential-user:
            authorization-grant-type: authorization_code
            client-name: a local Keycloak instance
            client-id: client-ui
            client-secret: ${keycloak-secret}
            provider: keycloak-realm-id
            scope: openid,profile,email,offline_access

/src/main/resources/templates/greet.html:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <title>Greetings!</title>
</head>

<body>
    <h1 th:utext="${message}">..!..</h1>
</body>

Combining oauth2Login and oauth2ResourceServer

If your app exposes both a REST API to be consumed by an OAuth2 client and some other resources to be queried by a browser (without the help of an OAuth2 client lib for a framework like Angular, React or Vue), and only in that case, define the two different filter chains above and add a securityMatcher to the first in order to limit it to the routes it should secure.

In the samples provided above, the security filter-chain with the lowest order is the resource server one. Adding something like http.securityMatcher("/api/**"); would do the trick: all other routes will be secured with the client filter-chain which is tried after and will intercept all requests that were not already intercepted by the resource server filter chain.

Complete tutorial.

like image 183
ch4mp Avatar answered May 27 '26 06:05

ch4mp



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!