1

I have an HTTP Spring Security configuration that appears to work when I comment out each individual aspect but it doesn't work when I combine the Spring Security rules together, so I know the problem is not with the regexMatcher or the antMatcher but with the rules applied in combination.

Here is my Spring Security class:

package com.driver.website.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;
import java.security.AccessControlContext;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${widget.headers.xframeoptions.domains.allowed}")
    private String allowedXFrameOptions;

    @Value("${widget.headers.origins.allowed}")
    private String allowedOrigins;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off

        http.exceptionHandling().accessDeniedPage("/login")
                .and()
                .formLogin().loginPage("/login").defaultSuccessUrl("/myaccount", true).permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/**").permitAll();

        http.regexMatcher("^((?!(/widget|/assistedSearch)).)*$")
                .headers().frameOptions().disable()
                .regexMatcher("^((?!(/widget|/assistedSearch)).)*$")
                .headers()
                .xssProtection()
                .contentTypeOptions()
                .addHeaderWriter(new StaticHeadersWriter("X-FRAME-OPTIONS", "SAMEORIGIN"));

        http.antMatcher("/widget")
            .headers()
            .frameOptions()
            .disable()
            .antMatcher("/widget")
            .headers()
            .addHeaderWriter(new StaticHeadersWriter("X-FRAME-OPTIONS", "ALLOW-FROM " + allowedXFrameOptions));

        http.requestMatchers().antMatchers("/assistedSearch", "/widget")
            .and()
            .headers()
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Origin", allowedOrigins))
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Methods", "GET, POST"))
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Headers", "Content-Type"));

        // @formatter:on
    }
}

The rules should be...

  • For all urls but not /widget and /assistedSearch we should add the SAMEORIGIN X-Frame-Options header
  • For the /widget endpoint we should add the X-Frame-Options: ALLOW-FROM header
  • For the /widget and /assistedSearch endpoints we should add the Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers headers

As I mentioned above if I comment out the For all urls ruleset then the other two work in unison, but with the For all urls rule uncommented none of the headers appear.

Does anyone have any ideas why this might be? How do you add multiple rulesets in Spring Security and override existing rulesets with new ones?

I tried

http.antMatcher("/widget")
    .headers()
    .frameOptions()
    .disable()

Which again appears to work on it's own but not in combination.

Thanks in advance!

James Murphy
  • 800
  • 1
  • 15
  • 29
  • 4
    You can only add not overwrite rules. Also only the first match will be applied so having multiple rules with the same pattern (or possible matching pattern) will not work as only the first match will win. The rules are also processed in the order they are defined in. As your `/**` is defined first all your other rules are pretty much useless. It is clearly (and in bold if I recall correctly) in the reference guide that `/**` should always be the last one in the chain. – M. Deinum Sep 12 '16 at 19:19
  • @M.Deinum I've moved the `everything but not /widget and /assistedSearch` ruleset as the last declaration alas it means that that rule is applied correctly but the /widget and /assistedSearch rules are not. Now I understand that you've stated they are additive but I technically only added one ruleset to /widget and /assistedSearch so why would it mean that no headers are showing whatsoever? – James Murphy Sep 12 '16 at 19:35
  • So for this question to be downgraded is pretty ridiculous. It's a valid one. I'm specifying mutually exclusive rulesets here. Why offer the power of regular expressions if you can't use mutually exclusive rulesets? Is anyone aware of the best practise for getting around this issue. Do I have to instead specify interceptors rather than handle it using the DSL? – James Murphy Sep 12 '16 at 20:06
  • 1
    YOu cannot have to `antMatchers("/widget")` as then only 1 will apply not both. I meant to say that each matcher pattern is added, once. For `/widget` you have 3 matchers, but only one, probably the first will be applied, not all off them. – M. Deinum Sep 13 '16 at 05:33
  • Thanks for the assistance. So basically I'll need a static class containing all the rules for '/widget' and a separate ruleset for '/assistedSearch'. Sure it will mean duplicate rules for both but at least then there's separation of concerns and more importantly it will work. I'll look into that and post my solution. – James Murphy Sep 13 '16 at 05:40

1 Answers1

6

You override your previous matchers, see HttpSecurity.html#antMatcher:

Invoking antMatcher(String) will override previous invocations of mvcMatcher(String)}, requestMatchers(), antMatcher(String), regexMatcher(String), and requestMatcher(RequestMatcher).

and HttpSecurity.html#regexMatcher:

Invoking regexMatcher(String) will override previous invocations of mvcMatcher(String)}, requestMatchers(), antMatcher(String), regexMatcher(String), and requestMatcher(RequestMatcher).

If you want more than one configuration of HttpSecurity, see Spring Security Reference:

We can configure multiple HttpSecurity instances just as we can have multiple <http> blocks. The key is to extend the WebSecurityConfigurationAdapter multiple times. For example, the following is an example of having a different configuration for URL’s that start with /api/.

@EnableWebSecurity
public class MultiHttpSecurityConfig {
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) { 1
      auth
          .inMemoryAuthentication()
              .withUser("user").password("password").roles("USER").and()
              .withUser("admin").password("password").roles("USER", "ADMIN");
  }

  @Configuration
  @Order(1)                                                        2
  public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
      protected void configure(HttpSecurity http) throws Exception {
          http
              .antMatcher("/api/**")                               3
              .authorizeRequests()
                  .anyRequest().hasRole("ADMIN")
                  .and()
              .httpBasic();
      }
  }

  @Configuration                                                   4
  public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http
              .authorizeRequests()
                  .anyRequest().authenticated()
                  .and()
              .formLogin();
      }
  }
}
dur
  • 15,689
  • 25
  • 79
  • 125
  • Thanks for this answer. I implemented static classes for each of my rules and then applied them using the correct @Order for each. Once you wrap your head around how Spring Security works behind the scenes then it all makes sense. – James Murphy Sep 26 '16 at 09:06