[spring-mvc] Serving static web resources in Spring Boot & Spring Security application

I am trying to develop Spring Boot web application and securing it using Spring security java configuration.

After placing my static web resources in 'src/main/resources/public' as advised here in Spring blog, I am able to get the static resources. i.e hitting https://localhost/test.html in browser do serves the html content.

Problem

After I enabled Spring Security, hitting the static resource URL requires authentication.

My relevent Spring Security Java config looks like this:-

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.
            authorizeRequests()
                .antMatchers("/","/public/**", "/resources/**","/resources/public/**")
                    .permitAll()
                .antMatchers("/google_oauth2_login").anonymous()
                    .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/")
                    .loginProcessingUrl("/login")
                    .defaultSuccessUrl("/home")
                    .and()
                    .csrf().disable()
                    .logout()
                        .logoutSuccessUrl("/")
                        .logoutUrl("/logout") // POST only
                .and()
                    .requiresChannel()
                    .anyRequest().requiresSecure()
                .and()
                    .addFilterAfter(oAuth2ClientContextFilter(),ExceptionTranslationFilter.class)
                    .addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
                .userDetailsService(userService);
        // @formatter:on
    }

How should I configure antMatchers to permit static resources placed inside src/main/resources/public ?

This question is related to spring-mvc spring-security spring-boot

The answer is


  @Override
      public void configure(WebSecurity web) throws Exception {
        web
          .ignoring()
             .antMatchers("/resources/**"); // #3
      }

Ignore any request that starts with "/resources/". This is similar to configuring http@security=none when using the XML namespace configuration.


Here is the ultimate solution, after 20+ hours of research.

Step 1. Add 'MvcConfig.java' to your project.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
                .addResourceHandler("/resources/**")
                .addResourceLocations("/resources/");
    }
}

Step 2. Add configure(WebSecurity web) override to your SecurityConfig class

@Override
    public void configure(WebSecurity web) throws Exception {
        web
                .ignoring()
                .antMatchers("/resources/**");
    }

Step 3. Place all static resources in webapp/resources/..


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        String[] resources = new String[]{
                "/", "/home","/pictureCheckCode","/include/**",
                "/css/**","/icons/**","/images/**","/js/**","/layer/**"
        };

        http.authorizeRequests()
                .antMatchers(resources).permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout().logoutUrl("/404")
                .permitAll();
        super.configure(http);
    }
}

This may be an answer (for spring boot 2) and a question at the same time. It seems that in spring boot 2 combined with spring security everything (means every route/antmatcher) is protected by default if you use an individual security mechanism extended from

WebSecurityConfigurerAdapter

If you don´t use an individual security mechanism, everything is as it was?

In older spring boot versions (1.5 and below) as Andy Wilkinson states in his above answer places like public/** or static/** are permitted by default.

So to sum this question/answer up - if you are using spring boot 2 with spring security and have an individual security mechanism you have to exclusivley permit access to static contents placed on any route. Like so:

@Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

private final ThdAuthenticationProvider thdAuthenticationProvider;

private final ThdAuthenticationDetails thdAuthenticationDetails;

/**
 * Overloaded constructor.
 * Builds up the needed dependencies.
 *
 * @param thdAuthenticationProvider a given authentication provider
 * @param thdAuthenticationDetails  given authentication details
 */
@Autowired
public SpringSecurityConfiguration(@NonNull ThdAuthenticationProvider thdAuthenticationProvider,
                                   @NonNull ThdAuthenticationDetails thdAuthenticationDetails) {
    this.thdAuthenticationProvider = thdAuthenticationProvider;
    this.thdAuthenticationDetails = thdAuthenticationDetails;
}

/**
 * Creates the AuthenticationManager with the given values.
 *
 * @param auth the AuthenticationManagerBuilder
 */
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {

    auth.authenticationProvider(thdAuthenticationProvider);
}

/**
 * Configures the http Security.
 *
 * @param http HttpSecurity
 * @throws Exception a given exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests()
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
            .antMatchers("/management/**").hasAnyAuthority(Role.Role_Engineer.getValue(),
            Role.Role_Admin.getValue())
            .antMatchers("/settings/**").hasAnyAuthority(Role.Role_Engineer.getValue(),
            Role.Role_Admin.getValue())

            .anyRequest()
            .fullyAuthenticated()
            .and()
            .formLogin()
            .authenticationDetailsSource(thdAuthenticationDetails)
            .loginPage("/login").permitAll()
            .defaultSuccessUrl("/bundle/index", true)
            .failureUrl("/denied")
            .and()
            .logout()
            .invalidateHttpSession(true)
            .logoutSuccessUrl("/login")
            .logoutUrl("/logout")
            .and()
            .exceptionHandling()
            .accessDeniedHandler(new CustomAccessDeniedHandler());
}

}

Please mind this line of code, which is new:

.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()

If you use spring boot 1.5 and below you don´t need to permit these locations (static/public/webjars etc.) explicitly.

Here is the official note, what has changed in the new security framework as to old versions of itself:

Security changes in Spring Boot 2.0 M4

I hope this helps someone. Thank you! Have a nice day!


If you are using webjars. You need to add this in your configure method: http.authorizeRequests().antMatchers("/webjars/**").permitAll();

Make sure this is the first statement. For example:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/webjars/**").permitAll();
        http.authorizeRequests().anyRequest().authenticated();
         http.formLogin()
         .loginPage("/login")
         .failureUrl("/login?error")
         .usernameParameter("email")
         .permitAll()
         .and()
         .logout()
         .logoutUrl("/logout")
         .deleteCookies("remember-me")
         .logoutSuccessUrl("/")
         .permitAll()
         .and()
         .rememberMe();
    }

You will also need to have this in order to have webjars enabled:

@Configuration
    public class MvcConfig extends WebMvcConfigurerAdapter {
        ...
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
        ...
    }

i had the same issue with my spring boot application, so I thought it will be nice if i will share with you guys my solution. I just simply configure the antMatchers to be suited to specific type of filles. In my case that was only js filles and js.map. Here is a code:

   @Configuration
   @EnableWebSecurity
   public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests()
      .antMatchers("/index.html", "/", "/home", 
       "/login","/favicon.ico","/*.js","/*.js.map").permitAll()
      .anyRequest().authenticated().and().csrf().disable();
   }
  }

What is interesting. I find out that resources path like "resources/myStyle.css" in antMatcher didnt work for me at all. If you will have folder inside your resoruces folder just add it in antMatcher like "/myFolder/myFille.js"* and it should work just fine.


Examples related to spring-mvc

Two Page Login with Spring Security 3.2.x ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean Spring 5.0.3 RequestRejectedException: The request was rejected because the URL was not normalized The type WebMvcConfigurerAdapter is deprecated RestClientException: Could not extract response. no suitable HttpMessageConverter found Spring boot: Unable to start embedded Tomcat servlet container UnsatisfiedDependencyException: Error creating bean with name 8080 port already taken issue when trying to redeploy project from Spring Tool Suite IDE Error creating bean with name 'entityManagerFactory' defined in class path resource : Invocation of init method failed Difference between the annotations @GetMapping and @RequestMapping(method = RequestMethod.GET)

Examples related to spring-security

Two Page Login with Spring Security 3.2.x Spring 5.0.3 RequestRejectedException: The request was rejected because the URL was not normalized Unsupported Media Type in postman How Spring Security Filter Chain works Spring security CORS Filter How to configure CORS in a Spring Boot + Spring Security application? Failed to load ApplicationContext (with annotation) disabling spring security in spring boot app When to use Spring Security`s antMatcher()? How to manage exceptions thrown in filters in Spring?

Examples related to spring-boot

Access blocked by CORS policy: Response to preflight request doesn't pass access control check Why am I getting Unknown error in line 1 of pom.xml? Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured How to resolve Unable to load authentication plugin 'caching_sha2_password' issue ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName ERROR Source option 1.5 is no longer supported. Use 1.6 or later How to start up spring-boot application via command line? JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value