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.
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 ...
}