Serving static web resources in Spring Boot & Spring Security application
Asked Answered
B

11

100

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 ?

Bloodline answered 23/7, 2014 at 17:17 Comment(2)
See also #24164514Romeliaromelle
Note that somewhere down the line you may need to add content security headers to your static content (including any default/custom error pages) to prevent clickjacking etc exploitsQuinine
M
124

There are a couple of things to be aware of:

  • The Ant matchers match against the request path and not the path of the resource on the filesystem.
  • Resources placed in src/main/resources/public will be served from the root of your application. For example src/main/resources/public/hello.jpg would be served from http://localhost:8080/hello.jpg

This is why your current matcher configuration hasn't permitted access to the static resources. For /resources/** to work, you would have to place the resources in src/main/resources/public/resources and access them at http://localhost:8080/resources/your-resource.

As you're using Spring Boot, you may want to consider using its defaults rather than adding extra configuration. Spring Boot will, by default, permit access to /css/**, /js/**, /images/**, and /**/favicon.ico. You could, for example, have a file named src/main/resources/public/images/hello.jpg and, without adding any extra configuration, it would be accessible at http://localhost:8080/images/hello.jpg without having to log in. You can see this in action in the web method security smoke test where access is permitted to the Bootstrap CSS file without any special configuration.

Marinemarinelli answered 23/7, 2014 at 20:54 Comment(6)
- I've cloned the spring boot sample repo and run the example ( web method security sample). It is not working. localhost:8080/css/bootstrap.min.css is redirected to login page. - It is different implemented as described solution. Static file has path: src/main/resources/static/css/Crissman
See the answer of Thomas Lang if you are using Spring Boot 2Twinberry
static or css js should be inside src/main/resources/public here public folder is the key. ThanksSaphena
i think this is needed http.authorizeRequests().antMatchers("/css/**").permitAll()Basilisk
I used web.ignoring().antMatchers("/static/**"); in order to gain access to static resources, but now spring security keeps redirecting me to css and display a 404 page after login, not to the home page. The home page is displayed only after a refresh. I am not using spring boot, but only spring MVC and spring security with the @EnableWebSecurity annotation to activate it.Bromley
@Andy Wilkinson can you please re-upload linked web method security sample that you've mentioned in your answer? Thank you in advance.Hultin
S
37
  @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.

Sheedy answered 23/7, 2014 at 18:15 Comment(1)
Does not work for me, either. While I'm loading my static html from an API, and refer to one of my static files at /resources/css/main.css. The html page rendered by Rest API works fine. However, static css does not.Transmission
I
34

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!

Internalize answered 27/3, 2018 at 6:54 Comment(4)
I can confirm that adding the extra line fixed it for me (Spring Boot 2.0.3)Twinberry
Extra line does help a lot but I need to add few more lines to make it working. Boot version 2.0.6. (1) .antMatchers("/", "/callback", "/login**", "/webjars/**", "/error**", "/static/**").permitAll() and (2) registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); under WebMvcConfigurer.addResourceHandlers().Colored
Thank you so much for this!Pantechnicon
@Thomas, I wish this could work for me as well, but unfortunately this is not working (Spring boot 2.7). I have tried many ways like antMatchers and placing to public directory and also the method suggested by you. I am able to access other urls, the custom login page without css. Basically none of my static content is accessible after adding custom locin page. Could there be something I am missing or anything else to do in Sping boot 2.7Amplitude
S
27

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

Suisse answered 30/9, 2016 at 8:24 Comment(2)
Could you explain what are you doing and why? "Step1": add static resource handling. "Step2": remove static resource handling.Abner
If someone uses XML configuration then in step 1 you can use this line <mvc:resources mapping="/resources/**" location="/resources/" /> in your dispatcher-servlet.xml instead of creating new Java config class.Poach
S
9

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/");
        }
        ...
    }
Studner answered 8/8, 2015 at 14:0 Comment(1)
WebMvcConfigurerAdapter is deprecated, So you can use WebMvcConfigurationSupportHeeling
M
8
@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);
    }
}
Mismate answered 21/1, 2018 at 4:21 Comment(1)
Will calling super.configure not enable Basic Auth?Eadwine
R
4

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.

Reynolds answered 26/6, 2018 at 21:0 Comment(1)
For the ones wanting most resources: http.authorizeRequests().antMatchers(HttpMethod.GET, "/", "/index.html", "/favicon.ico", "/**/*.js", "/**/*.js.map", "/**/*.css", "/assets/images/*.png", "/assets/images/*.jpg", "/assets/images/*.jpeg", "/assets/images/*.gif", "/**/*.ttf", "/**/*.json", "/**/*.woff", "/**/*.woff2", "/**/*.eot", "/**/*.svg").permitAll() If you're wondering why double ** . With ** it means permit anywhere where there is a file with that extension. Also NOTE the HTTPMETHOD.GET. Replace /assets/images with your own folder. Otherwise just put /*.jpgBartholemy
L
4

In the latest Spring Security 6, the WebSecurityConfigurerAdapter is deprecated.

Declare a WebSecurityCustomizer bean instead.

 @Bean
 public WebSecurityCustomizer ignoringCustomizer() {
     return (web) -> web.ignoring().requestMatchers("...");
 }
Lamkin answered 20/4, 2022 at 2:0 Comment(0)
K
3

It,s work for spring security 6.0.*

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {


        http
                .csrf()
                .disable()
                .authorizeHttpRequests()
                .requestMatchers(
                        "/home/**",
                        "/login/**",
                        "/account/starter/**",
                        "/register/**",
                        "/plugins/**",
                        "/dist/**",
                        "/js/**",
                        "/**/favicon.ico").permitAll()
                .and()
                .httpBasic()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        return http.build();
    }

 
                        "/plugins/**",
                        "/dist/**",
                        "/js/**",

... they are located in resources/

plugins, dist, js - these are the names of directories with resources

Kathrinkathrine answered 1/1, 2023 at 17:34 Comment(0)
N
1

Another Spring Security 6 example, which follows the documentation's suggestion "Favor permitAll over ignoring" and uses PathRequest to create static resources request matcher:

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                .securityMatcher("/**")
                .authorizeHttpRequests(
                        authorizationManagerRequestMatcherRegistry ->
                                authorizationManagerRequestMatcherRegistry
                                        .requestMatchers(
                                                PathRequest
                                                        .toStaticResources()
                                                        .atCommonLocations())
                                        .permitAll()
                                        .requestMatchers("/**")
                                        .fullyAuthenticated()
                )
                
                ...
                
                ;

        return http.build();
    }
Nanananak answered 28/7, 2023 at 10:3 Comment(3)
I have code almost same as this. But it does not work. Mine is .....requestMatchers( "/css/**").permitAll().anyRequest().authenticated(). When i access localhost:8080/css/myCss.css, i get "No Mapping for GET /css/myCss.css"Selfacting
myCss.css is located under resources/static/css/myCss.cssSelfacting
@NickWills "No Mapping for GET..." errors are not related to the Spring Security config.Nanananak
G
0

Solution in Spring Boot 3+, Spring Security 6+, and JWT

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    JWTTokenValidatorFilter jwtTokenValidatorFilter = new JWTTokenValidatorFilter();
    jwtTokenValidatorFilter.setSecret(jwtSecret);

    http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(
                    request -> request
                            .requestMatchers("/static/**", "/actuator/**", "/api/v1/auth/**")
                            .permitAll()
                            .anyRequest().permitAll())
            .sessionManagement(manager -> manager.sessionCreationPolicy(STATELESS))
            .exceptionHandling(
                    (exceptionHandling) -> exceptionHandling
                            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                    .accessDeniedPage("/error")
            )
            .addFilterBefore(jwtTokenValidatorFilter, BasicAuthenticationFilter.class);
    return http.build();
}
Gluttony answered 9/10, 2023 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.