Describe the bug A clear and concise description of what the bug is.
Even after configuring authorizeHttpRequests to permit all requests for /js/ and /css/, and setting headers(headers -> headers.cacheControl(Customizer.withDefaults())), the JavaScript (js) and CSS files are still not being cached and fetched from the server every time.
To Reproduce Steps to reproduce the behavior. Open Expected behavior A clear and concise description of what you expected to happen. 1. When opening the page and log in. 2.Each time, the response headers indicate "no-cache, no-store, max-age=0, must-revalidate" for the JS and CSS files.
Sample
A link to a GitHub repository with a minimal, reproducible sample.
spring-security-core 5.3.9.RELEASE is OK!
spring-security-core 6.0.1 is error!
Reports that include a sample will take priority over reports that do not. At times, we may require a sample, so it is good to try and include a sample up front.
Comment From: jzheaux
Hi, @bu6030. By default, Spring Security configures responses so that contents are not cached.
Will you please share what you changed when migrating from 5.3 to 6.0? Perhaps in 5.3 you were using webSecurity.ignoring to ignore those endpoints and now you are using permitAll instead.
Comment From: bu6030
Hi, @jzheaux.
Yes, I used webSecurity.ignoring to ignore those static files and the contents are cached.
Is there any code to replace webSecurity.ignoring in 6.0?
The code 5.3:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.sql.DataSource;
import java.time.Duration;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
@Autowired
DataSource dataSource;
private final static String ACCOUNT_CLIENT_AUTHORITY = "ADMIN";
@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
@Override
public void configure(WebSecurity web) throws Exception {
// ignore
web.ignoring().antMatchers("/css/**", "/js/**", "/fonts/**", "/images/**",
"/i/**", "/resources/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("*");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(1)));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/chrome/**", "/css/**", "/js/**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.hasAuthority(ACCOUNT_CLIENT_AUTHORITY)
.and()
.csrf()
.disable()
.sessionManagement()
.disable()
.cors()
.and()
.headers().cacheControl()
.and()
.contentTypeOptions();
http.formLogin(withDefaults());
}
}
Comment From: jzheaux
What I'd recommend is handling your static resources in a separate filter chain like so:
@Bean
@Order(0)
SecurityFilterChain staticEndpoints(HttpSecurity http) throws Exception {
http
.securityMatcher("/css/**", "/js/**", "/fonts/**", "/images/**", "/i/**", "/resources/**")
.headers((headers) -> headers.cacheControl((cache) -> cache.disable()))
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll());
return http.build();
}
What this bean is saying is that the listed rules only apply to the URIs specified in securityMatcher.
This will go alongside a second bean definition for the rest of your apps endpoints:
@Bean
SecurityFilterChain appEndpoints(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/login", "/chrome/**").permitAll()
.anyRequest().hasAuthority(ACCOUNT_CLIENT_AUTHORITY)
)
.formLogin(Customizer.withDefaults())
// .. the rest of your existing configuration
return http.build();
}