javaspring-bootspring-securitymigration

Upgrade to Spring Boot 3 returns 403s for all pages, causes authorization check on JSP URIs


I had a working Spring Boot 2.7 app with HTTP Basic auth, using Spring Security and Spring MVC. After upgrading to Spring Boot 3.3, I get 403 responses on all pages, even ones that are marked permitAll(). After considerable debugging, it seems my user successfully passes an auth check for the web path of each page, but then fails a subsequent auth check for the JSP URI, because I haven't defined any matchers pointing to my WEB-INF.

RequestMatcherDelegatingAuthorizationManager - Authorizing GET /my-page/
RequestMatcherDelegatingAuthorizationManager - Checking authorization on GET /my-page/ using org.springframework.security.authorization.AuthenticatedAuthorizationManager@2488b87f
FilterChainProxy - Secured GET /my-page/
...
RequestMatcherDelegatingAuthorizationManager - Authorizing GET /WEB-INF/jsp/my-page.jsp
RequestMatcherDelegatingAuthorizationManager - Denying request since did not find matching RequestMatcher

Obviously, the client isn't requesting the JSPs directly; that's coming from my controller. Debugging RequestMatcherDelegatingAuthorizationManager in Spring Boot 2.7 shows only the web paths are checked; there's no followup check on the JSPs.

Here's my Spring Boot 3 security config:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class MyConfig
{
    @Bean
    public SecurityFilterChain mySecurityChainFilterChain(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity
            .authorizeHttpRequests(requests -> requests
                .requestMatchers("/").permitAll()
                .requestMatchers("/my-page/**").authenticated()
                .requestMatchers("/my-page/admin/**").hasRole("ADMIN")
            )
            .csrf().disable() // just to prove this isn't the problem
            .cors().disable() // just to prove this isn't the problem
            .authenticationManager(makeAuthenticationManager(httpSecurity))
            .httpBasic(Customizer.withDefaults());

        return httpSecurity.build();
    }

    private AuthenticationManager makeAuthenticationManager(HttpSecurity httpSecurity) throws Exception
    {
        AuthenticationManagerBuilder builder = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class);
        builder.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder);
        return builder.build();
    }

    // ... bean definitions ...
}

Funny enough, defining request matchers for my WEB-INF works...but I know this isn't the solution. Any help here would be super appreciated.


Solution

  • After struggling with it some more, I found I had to permit-all on FORWARDs, which quickly led to realizing I'd need to do the same for INCLUDEs.

    Here's my new 3.3 config:

    @Configuration
    @EnableWebSecurity
    @EnableMethodSecurity
    public class MyConfig
    {
        @Bean
        public SecurityFilterChain mySecurityChainFilterChain(HttpSecurity httpSecurity) throws Exception
        {
            httpSecurity
                .authorizeHttpRequests(requests -> requests
                    .dispatcherTypeMatchers(
                        DispatcherType.ERROR,
                        DispatcherType.FORWARD,
                        DispatcherType.INCLUDE
                    ).permitAll()
                    .requestMatchers("/my-page/admin/**").hasRole("ADMIN")
                    .requestMatchers("/my-page/**").authenticated()
                    .requestMatchers("/", "/public/assets/**").permitAll()
                )
                .authenticationManager(makeAuthenticationManager(httpSecurity))
                .httpBasic(Customizer.withDefaults());
    
            return httpSecurity.build();
        }
    
        // ... bean definitions ...
    }