I've just tried to upgrade our Spring Boot application from 2.7.5 to 3.0.0 GA. Everything seems to work fine except for one thing. Our application is a Jersey REST endpoint that fowards requests via SOAP to SAP. We use Swagger in order to document the REST endpoint. The REST endpoint should be protected via basic authentication, but the Open API generated by Swagger should not. This worked fine in Spring Boot 2.7.5, but no longer works in 3.0.0.

This is our WebSecurityConfig class upgraded to Spring Boot 3.0.0:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

  @Bean
  public SecurityFilterChain configure(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((requests) -> requests
            .requestMatchers("/openapi/openapi.yml").permitAll()
            .anyRequest().authenticated())
        .httpBasic();
    return http.build();
  }
}

Both the REST endpoint as well as the Open API servlet are protected via basic authentication, but only the REST endpoint should be. This seems to have something to do with spring-boot-starter-web-services. If I remove that dependency from the Maven POM, it works fine. I've attached a small sample that reproduces the issue.

This was previously raised as a question on Stackoverflow

test.zip

Comment From: wilkinsona

The change in behavior that you have observed is due to Spring Web Services depending on Spring MVC. As described in its javadoc, this changes the behavior of requestMatchers:

If the HandlerMappingIntrospector is available in the classpath, maps to an MvcRequestMatcher that does not care which HttpMethod is used. This matcher will use the same rules that Spring MVC uses for matching. For example, often times a mapping of the path "/path" will match on "/path", "/path/", "/path.html", etc. If the HandlerMappingIntrospector is not available, maps to an AntPathRequestMatcher.

If a specific RequestMatcher must be specified, use requestMatchers(RequestMatcher) instead

You're using Jersey so you probably don't want MVC-based matching. You can avoid the problem by using AntPathRequestMatcher directly instead:

  @Bean
  public SecurityFilterChain configure(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((requests) -> requests
            .requestMatchers(new AntPathRequestMatcher("/openapi/openapi.yml")).permitAll()
            .anyRequest().authenticated())
        .httpBasic();
    return http.build();
  }

Comment From: super-iterator

@wilkinsona - This issue persists. I tried this line .requestMatchers(new AntPathRequestMatcher("/openapi/openapi.yml")).permitAll() and Spring security still requires authentication for that path. I only have Spring MVC and OAuth2 dependencies in a green field project based on Spring 6.

Comment From: wilkinsona

@super-iterator This issue was about Jersey and Spring Web Services. If you're using Spring MVC it isn't relevant to your problem.

If you have any further questions, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.

If you believe that you have found a bug, please open a new issue and provide a complete yet minimal sample that reproduces the problem.

Comment From: super-iterator

@wilkinsona Thanks for the prompt answer!

I know the original question is related to other libraries, but the same issue applies to Spring MVC.

This code never permits the permitAll paths without requiring authentication, and the documentation doesn't seem to be clear about this:

  @Bean
  public SecurityFilterChain configure(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((requests) -> requests
            .requestMatchers("/openapi/openapi.yml").permitAll()
            .anyRequest().authenticated())
        .httpBasic();
    return http.build();

Comment From: wilkinsona

@super-iterator Please see my earlier comment.

Comment From: toretest

Can some one give me a working solution for how to set up this ?

setting: Spring boot 3.02 Resource server and Congnito config.

Problem: Http status : 403 on a postman call to "http://localhost:9000/home" The bearer token is coming from Cognito.

Code example:

@RestController
public class HomeEndpoint {

    @GetMapping("/home")
    public String home(){
        return "Hello JWT!";
    }
}

Resource Server config

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class ResourceServerConfig {

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors(
                        csrf -> csrf.disable()
                )
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(HttpMethod.GET, "/home","/health","/info").permitAll()
                        .requestMatchers(HttpMethod.GET, "/test/").).authenticated()
                )
                .sessionManagement(session-> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                ..oauth2ResourceServer().jwt();

        return http.build();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromIssuerLocation(issuerUri);
        jwtDecoder.setClaimSetConverter(new UsernameSubClaimConverterAdapter());

        return JwtDecoders.fromIssuerLocation(issuerUri);
    }
import java.util.Collections;
import java.util.Map;

public class UsernameSubClaimConverterAdapter implements Converter<Map<String, Object>, Map<String, Object>> {
    private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());

    private static final String COGNITO_GROUPS = "cognito:groups";
    private static final String SPRING_AUTHORITIES = "authorities";
    private static final String COGNITO_USERNAME = "username";
    private static final String SPRING_USER_NAME = "user_name";

    @Override
    public Map<String, Object> convert(Map<String, Object> claims) {
        Map<String, Object> convertedClaims = this.delegate.convert(claims);

        if (claims.containsKey("cognito:groups")) {
            convertedClaims.put("authorities", claims.get(COGNITO_GROUPS));
        }

        if (claims.containsKey("username")) {
            convertedClaims.put("user_name", claims.get(COGNITO_USERNAME));
        }
        return convertedClaims;
    }
}

Comment From: wilkinsona

@toretest You don't appear to be using Jersey or Spring Web Services so I can't see the connection between your comment and this issue. If you're looking for some help and guidance, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.

Comment From: mariusss11

Can anyone help me with my code? I thing I might have some trouble because of the SecurityConfig Here it is

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        http
                .csrf(AbstractHttpConfigurer::disable)  // Disable CSRF
                // Allow public access
                .authorizeHttpRequests(request -> request
                        .requestMatchers("/register", "/login").permitAll()
                        .anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults()) // Enable Basic Auth
                .sessionManagement(session ->
                        session.sessionCreationPolicy(
                                SessionCreationPolicy.STATELESS)) // Stateless session
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); // Add JWT filter

        return http.build();
    }

Comment From: philwebb

@mariusss11 Please ask questions like this on stackoverflow.com. Thanks!