Okta and SpringBoot 3.0.0

I am working on upgrading my SpringBoot 2.7.5 application to SpringBoot 3.0.0.

I have the current configuration in my application.yml which works.

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://myissuer.okta.com/oauth2/someotherstuffhere

In my SecurityConfig I do the following

package com.fedex.amp.stormgateway.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.Collections;

@Configuration
public class SecurityConfig{

    private static Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    private static final String ROLE_ACTUATOR = "ACTUATOR";

   @Value("${com.amp.storm.gateway.actuator.username}")
   private String actuatorUsername;
   @Value("${com.amp.storm.gateway.actuator.password}")
   private String actuatorPassword;

    @Bean
    @Order(1)
    @Profile("!test")
    public SecurityFilterChain filterChainBasicAuth(HttpSecurity http) throws Exception {
        // @formatter:off
        http.antMatcher("/actuator/**")
                .httpBasic()
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // if no credentials supplied in request,
                // then immediately fail the request rather than prompting for a login
                .exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                .and()
                .authorizeRequests()
                .anyRequest().hasRole(ROLE_ACTUATOR);
        // @formatter:on
        logger.info("Loading Actuator Security Config");
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer()  {
        return (web) -> web.ignoring().requestMatchers(EndpointRequest.to("info", "health"));
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizerSwagger()  {
        return (web) -> web.ignoring().antMatchers("/swagger-ui/**", "/v3/api-docs/**");
    }

    @Bean
    public UserDetailsService sampleUserDetails() {
        //@formatter:off
        UserDetails user = User
                .withUsername(actuatorUsername)
                .password("{bcrypt}" + new BCryptPasswordEncoder().encode(actuatorPassword))
                .roles(ROLE_ACTUATOR).build();
        return new InMemoryUserDetailsManager(Collections.singletonList(user));
        //@formatter:on
    }

    @Bean
    @Order(2)
    @Profile("!test")
    public SecurityFilterChain filterChainOkta(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
        		.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
        		.and()
        		.headers().frameOptions().sameOrigin()
        		.and()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                    .csrf().disable()
                    .oauth2ResourceServer().jwt();

        return http.build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
    	CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

The above worked exactly as I expected. Since moving to SpringBoot 3 I have no changed my application.yml at all but I did update my Security config like so below.

@Configuration
public class SecurityConfig{

    private static Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    private static final String ROLE_ACTUATOR = "ACTUATOR";

   @Value("${com.amp.storm.gateway.actuator.username}")
   private String actuatorUsername;
   @Value("${com.amp.storm.gateway.actuator.password}")
   private String actuatorPassword;

    @Bean
    @Order(1)
    @Profile("!test")
    public SecurityFilterChain filterChainBasicAuth(HttpSecurity http) throws Exception {
        // @formatter:off

        http.authorizeHttpRequests(auth -> auth.requestMatchers("/actuators/**"))
                .httpBasic()
                .and()
                .csrf(csrf -> csrf.disable())
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                .and()
                .authorizeHttpRequests()
                .anyRequest()
                .hasRole((ROLE_ACTUATOR));
        // @formatter:on
        logger.info("Loading Actuator Security Config");
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer()  {
        return (web) -> web.ignoring().requestMatchers(EndpointRequest.to("info", "health"));
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizerSwagger()  {
        return (web) -> web.ignoring().requestMatchers("/swagger-ui/**", "/v3/api-docs/**");
    }

    @Bean
    public UserDetailsService sampleUserDetails() {
        //@formatter:off
        UserDetails user = User
                .withUsername(actuatorUsername)
                .password("{bcrypt}" + new BCryptPasswordEncoder().encode(actuatorPassword))
                .roles(ROLE_ACTUATOR).build();
        return new InMemoryUserDetailsManager(Collections.singletonList(user));
        //@formatter:on
    }

    @Bean
    @Order(2)
    @Profile("!test")
    public SecurityFilterChain filterChainOkta(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(auth -> auth.requestMatchers("/**"))
                .cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
                .and()
                .headers().frameOptions().sameOrigin()
                .and()
                .authorizeHttpRequests().anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf(csrf -> csrf.disable())
                .oauth2ResourceServer().jwt();

        return http.build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
    	CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

However now I get the following error at run time.

Method filterChainOkta in com.fedex.amp.stormgateway.security.SecurityConfig required a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found.

Does anyone know if there have been updates to variable pathing or some other change that would impact okta configuration and spring boot 3.0.0?

I looked at the Okta Spring Boot GithUb but I didn’t see any pull requests directly relating to this and examples seem to rely on the deprecated WebSecurityConfigurationAdapter.

Hello @howellevans,

Are you using okta-spring-boot where the current support matrix can be found here, or Spring OAuth2?

Thank You,

I am

implementation 'com.okta.spring:okta-spring-boot-starter:2.1.5'

Though Spring Boot 3.0 isn’t the compatibility matrix, so that may be an answer in and of itself.