Spring Boot 3 BeanCreationException: Error creating bean with name authorizationEndpoint - ClassNotFoundException: javax.servlet.ServletException

Viewed 132

I have a simple-as-possible OAuth2 JWT Spring Boot 2.7.3 project that works perfectly. It's easy to setup and run as described in the README from Github repository.

Besides Spring Security, the project uses spring-security-oauth2:

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>

Then defines some beans:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey(jwtSecret);
        return tokenConverter;
    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

And an Authorization Server:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Value("${security.oauth2.client.client-id}")
    private String clientId;
    
    @Value("${security.oauth2.client.client-secret}")
    private String clientSecret;
    
    @Value("${jwt.duration}")
    private Integer jwtDuration;
    
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;
    
    @Autowired
    private JwtTokenStore tokenStore;
    
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
        .withClient(clientId)
        .secret(passwordEncoder.encode(clientSecret))
        .scopes("read", "write")
        .authorizedGrantTypes("password")
        .accessTokenValiditySeconds(jwtDuration);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.authenticationManager(authenticationManager)
        .tokenStore(tokenStore)
        .accessTokenConverter(accessTokenConverter);
    }
}

And a Resource Server:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Value("${cors.origins}")
    private String corsOrigins;

    @Autowired
    private Environment env;
    
    @Autowired
    private JwtTokenStore tokenStore;
    
    private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" };

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        // H2
        if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
            http.headers().frameOptions().disable();
        }
        
        http.authorizeRequests()
        .antMatchers(PUBLIC).permitAll()
        .anyRequest().authenticated();
        
        http.cors().configurationSource(corsConfigurationSource());
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {

        String[] origins = corsOrigins.split(",");

        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOriginPatterns(Arrays.asList(origins));
        corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH"));
        corsConfig.setAllowCredentials(true);
        corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
     
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);
        return source;
    }
     
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> bean
                = new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource()));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

Now I'm trying to upgrade it to Spring Boot 3.0.0-SNAPSHOT, but the project is throwing BeanCreationException: Error creating bean with name 'authorizationEndpoint'. The stack trace shows it's caused by ClassNotFoundException: javax.servlet.ServletException.

How to fix that?

3 Answers

The problem is that you are using an incompatible version of spring cloud (Hoxton.SR8). It is only compatible with spring boot 2.3.x and 2.2.x (see https://spring.io/blog/2020/08/28/spring-cloud-hoxton-sr8-has-been-released). You need to upgrade spring cloud as well.

(Spring boot 3 uses the new Jakarta APIs so jakarta.servlet.ServletException is only available and downloaded by maven after you upgraded Spring boot. Your incompatible spring cloud version expects the old javax.servlet.ServletException class, but is not there anymore. Therefore this ClassNotFoundException.)

When I first ran the application(converted to gradle), I got the exception java.lang.ClassNotFoundException: javax.servlet.Filter. It went away once I added the javax-servlet-api dependency. Can you also try adding the below dependency?

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
Related