Skip to content

Commit

Permalink
Add support checking same security matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
franticticktick committed Nov 28, 2024
1 parent ff7dbb4 commit 7fb98ba
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.DefaultFilterChainValidator;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
Expand Down Expand Up @@ -338,6 +339,7 @@ else if (!this.observationRegistry.isNoop()) {
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
}
filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
filterChainProxy.setFilterChainValidator(new DefaultFilterChainValidator());
filterChainProxy.afterPropertiesSet();

Filter result = filterChainProxy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator {

Expand All @@ -75,11 +74,12 @@ private void checkPathOrder(List<SecurityFilterChain> filterChains) {
// Check that the universal pattern is listed at the end, if at all
Iterator<SecurityFilterChain> chains = filterChains.iterator();
while (chains.hasNext()) {
RequestMatcher matcher = ((DefaultSecurityFilterChain) chains.next()).getRequestMatcher();
if (AnyRequestMatcher.INSTANCE.equals(matcher) && chains.hasNext()) {
throw new IllegalArgumentException("A universal match pattern ('/**') is defined "
+ " before other patterns in the filter chain, causing them to be ignored. Please check the "
+ "ordering in your <security:http> namespace or FilterChainProxy bean configuration");
if (chains.next() instanceof DefaultSecurityFilterChain securityFilterChain) {
if (AnyRequestMatcher.INSTANCE.equals(securityFilterChain.getRequestMatcher()) && chains.hasNext()) {
throw new IllegalArgumentException("A universal match pattern ('/**') is defined "
+ " before other patterns in the filter chain, causing them to be ignored. Please check the "
+ "ordering in your <security:http> namespace or FilterChainProxy bean configuration");
}
}
}
}
Expand All @@ -88,10 +88,13 @@ private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
while (chains.size() > 1) {
DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain) chains.remove(0);
for (SecurityFilterChain test : chains) {
if (chain.getRequestMatcher().equals(((DefaultSecurityFilterChain) test).getRequestMatcher())) {
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the"
+ " matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace "
+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
if (test instanceof DefaultSecurityFilterChain securityFilterChain) {
if (chain.getRequestMatcher().equals(securityFilterChain.getRequestMatcher())) {
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the"
+ " matcher " + chain.getRequestMatcher()
+ ". If you are using multiple <http> namespace "
+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,9 +27,11 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
Expand All @@ -52,6 +54,7 @@
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

/**
* @author Rob Winch
Expand Down Expand Up @@ -156,6 +159,12 @@ public void ignoringMvcMatcherServletPath() throws Exception {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}

@Test
public void configureWhenSameSecurityMatchersConfiguredThenThrowsBeanCreationException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> loadConfig(MultipleSecurityMatchersConfig.class));
}

public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
Expand All @@ -169,6 +178,27 @@ static class DefaultConfig {

}

@Configuration
@EnableWebMvc
@EnableWebSecurity
static class MultipleSecurityMatchersConfig {

@Bean
@Order(0)
SecurityFilterChain app(HttpSecurity http) throws Exception {
http.securityMatcher("/app/**").authorizeHttpRequests((auth) -> auth.anyRequest().authenticated());
return http.build();
}

@Bean
@Order(1)
SecurityFilterChain api(HttpSecurity http) throws Exception {
http.securityMatcher("/app/**").authorizeHttpRequests((auth) -> auth.anyRequest().authenticated());
return http.build();
}

}

@EnableWebSecurity
@Configuration
@EnableWebMvc
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import jakarta.servlet.http.HttpServletRequest;

Expand Down Expand Up @@ -81,6 +82,23 @@ public MatchResult matcher(HttpServletRequest request) {
return MatchResult.notMatch();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OrRequestMatcher that = (OrRequestMatcher) o;
return Objects.equals(this.requestMatchers, that.requestMatchers);
}

@Override
public int hashCode() {
return Objects.hash(this.requestMatchers);
}

@Override
public String toString() {
return "Or " + this.requestMatchers;
Expand Down

0 comments on commit 7fb98ba

Please sign in to comment.