Skip to content

Polish SecurityFilterChain Validation #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

GuusArts
Copy link
Owner

@GuusArts GuusArts commented Mar 25, 2025

User description

Issue spring-projectsgh-15982


PR Type

Enhancement, Tests, Bug fix


Description

  • Introduced UnreachableFilterChainException to improve error handling for invalid SecurityFilterChain configurations.

  • Enhanced DefaultFilterChainValidator to throw UnreachableFilterChainException for duplicate or misplaced matchers.

  • Added unit tests to validate new exception handling in DefaultFilterChainValidatorTests.

  • Implemented equals and hashCode methods for AndRequestMatcher and OrRequestMatcher to improve object comparison.


Changes walkthrough 📝

Relevant files
Enhancement
DefaultFilterChainValidator.java
Enhanced validation logic for `DefaultFilterChainValidator`

config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java

  • Replaced generic exceptions with UnreachableFilterChainException.
  • Improved validation logic for duplicate and misplaced matchers.
  • Enhanced readability with pattern matching for
    DefaultSecurityFilterChain.
  • +23/-13 
    UnreachableFilterChainException.java
    Added `UnreachableFilterChainException` for invalid filter chains

    web/src/main/java/org/springframework/security/web/UnreachableFilterChainException.java

  • Introduced a new exception class UnreachableFilterChainException.
  • Added fields to capture invalid and unreachable filter chains.
  • Documented usage and purpose of the exception.
  • +55/-0   
    AndRequestMatcher.java
    Enhanced `AndRequestMatcher` with equality methods             

    web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java

  • Added equals and hashCode methods for object comparison.
  • Updated copyright year to 2024.
  • +19/-1   
    OrRequestMatcher.java
    Enhanced `OrRequestMatcher` with equality methods               

    web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java

  • Added equals and hashCode methods for object comparison.
  • Updated copyright year to 2024.
  • +19/-1   
    Tests
    DefaultFilterChainValidatorTests.java
    Added unit tests for enhanced filter chain validation       

    config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java

  • Added a test case for UnreachableFilterChainException.
  • Validated behavior for duplicate request matchers.
  • Improved test coverage for DefaultFilterChainValidator.
  • +23/-0   
    Additional files
    org.springframework.security.web.UnreachableFilterChainException.serialized [link]   

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Copy link

    sourcery-ai bot commented Mar 25, 2025

    Reviewer's Guide by Sourcery

    This pull request enhances the validation of SecurityFilterChain configurations by introducing a new exception, UnreachableFilterChainException, to provide more context when filter chains are unreachable due to configuration issues like duplicate matchers or incorrect ordering. It also adds equals and hashCode implementations to AndRequestMatcher and OrRequestMatcher.

    Class diagram for UnreachableFilterChainException

    classDiagram
      class UnreachableFilterChainException {
        -SecurityFilterChain filterChain
        -SecurityFilterChain unreachableFilterChain
        +UnreachableFilterChainException(String message, SecurityFilterChain filterChain, SecurityFilterChain unreachableFilterChain)
        +SecurityFilterChain getFilterChain()
        +SecurityFilterChain getUnreachableFilterChain()
      }
      IllegalArgumentException <|-- UnreachableFilterChainException
    
    Loading

    Updated class diagram for AndRequestMatcher

    classDiagram
      class AndRequestMatcher {
        -List~RequestMatcher~ requestMatchers
        +AndRequestMatcher(List~RequestMatcher~ requestMatchers)
        +AndRequestMatcher(RequestMatcher... requestMatchers)
        +boolean matches(HttpServletRequest request)
        +MatchResult matcher(HttpServletRequest request)
        +String toString()
        +boolean equals(Object o)
        +int hashCode()
      }
    
    Loading

    Updated class diagram for OrRequestMatcher

    classDiagram
      class OrRequestMatcher {
        -List~RequestMatcher~ requestMatchers
        +OrRequestMatcher(List~RequestMatcher~ requestMatchers)
        +OrRequestMatcher(RequestMatcher... requestMatchers)
        +boolean matches(HttpServletRequest request)
        +MatchResult matcher(HttpServletRequest request)
        +String toString()
        +boolean equals(Object o)
        +int hashCode()
      }
    
    Loading

    File-Level Changes

    Change Details Files
    Introduces UnreachableFilterChainException to provide more context when a SecurityFilterChain is unreachable.
    • Created a new exception class UnreachableFilterChainException extending IllegalArgumentException.
    • The exception includes references to the SecurityFilterChain that caused the exception and the unreachable SecurityFilterChain.
    web/src/main/java/org/springframework/security/web/UnreachableFilterChainException.java
    Updates DefaultFilterChainValidator to throw UnreachableFilterChainException instead of IllegalArgumentException when filter chain configuration issues are detected.
    • Modified the checkPathOrder method to throw UnreachableFilterChainException when a universal request matcher is defined before other matchers.
    • Modified the checkForDuplicateMatchers method to throw UnreachableFilterChainException when duplicate request matchers are found.
    • The thrown exception now includes references to the relevant filter chains.
    config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java
    Adds a test case to verify that UnreachableFilterChainException is thrown when duplicate request matchers are present.
    • Added a new test method validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainException to DefaultFilterChainValidatorTests.
    • The test configures two filter chains with the same request matcher and asserts that an UnreachableFilterChainException is thrown during validation.
    config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java
    Adds equals and hashCode implementations to AndRequestMatcher and OrRequestMatcher.
    • Added equals method to compare AndRequestMatcher instances based on their requestMatchers.
    • Added hashCode method to generate hash code based on the requestMatchers.
    • Added equals method to compare OrRequestMatcher instances based on their requestMatchers.
    • Added hashCode method to generate hash code based on the requestMatchers.
    web/src/main/java/org/springframework/security/web/util/matcher/AndRequestMatcher.java
    web/src/main/java/org/springframework/security/web/util/matcher/OrRequestMatcher.java

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!
    • Generate a plan of action for an issue: Comment @sourcery-ai plan on
      an issue to generate a plan of action for it.

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    Copy link

    Qodo Merge was enabled for this repository. To continue using it, please link your Git account with your Qodo account here.

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Potential Bug

    The checkForDuplicateMatchers method may not correctly identify all duplicate matchers. It only compares the current chain with the previous one, not with all previous chains in the list.

    private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
    	DefaultSecurityFilterChain filterChain = null;
    	for (SecurityFilterChain chain : chains) {
    		if (filterChain != null) {
    			if (chain instanceof DefaultSecurityFilterChain defaultChain) {
    				if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) {
    					throw new UnreachableFilterChainException(
    							"The FilterChainProxy contains two filter chains using the" + " matcher "
    									+ defaultChain.getRequestMatcher()
    									+ ". If you are using multiple <http> namespace "
    									+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.",
    							defaultChain, chain);
    				}
    			}
    		}
    		if (chain instanceof DefaultSecurityFilterChain defaultChain) {
    			filterChain = defaultChain;
    		}
    	}
    Equality Comparison

    The equals method compares requestMatchers directly with Objects.equals, which might not work correctly if the lists contain the same matchers but in different order.

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

    Copy link

    Qodo Merge was enabled for this repository. To continue using it, please link your Git account with your Qodo account here.

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Fix duplicate matcher detection

    The current implementation only checks for duplicate matchers between
    consecutive chains. This means if chains at positions 0 and 2 have the same
    matcher but chain 1 has a different matcher, the duplicate won't be detected.
    You should compare each chain against all other chains to properly detect all
    duplicates.

    config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java [89-107]

     private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
    -    DefaultSecurityFilterChain filterChain = null;
    -    for (SecurityFilterChain chain : chains) {
    -        if (filterChain != null) {
    -            if (chain instanceof DefaultSecurityFilterChain defaultChain) {
    -                if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) {
    -                    throw new UnreachableFilterChainException(
    -                            "The FilterChainProxy contains two filter chains using the" + " matcher "
    -                                    + defaultChain.getRequestMatcher()
    -                                    + ". If you are using multiple <http> namespace "
    -                                    + "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.",
    -                            defaultChain, chain);
    +    for (int i = 0; i < chains.size(); i++) {
    +        SecurityFilterChain current = chains.get(i);
    +        if (current instanceof DefaultSecurityFilterChain currentDefaultChain) {
    +            for (int j = i + 1; j < chains.size(); j++) {
    +                SecurityFilterChain other = chains.get(j);
    +                if (other instanceof DefaultSecurityFilterChain otherDefaultChain) {
    +                    if (currentDefaultChain.getRequestMatcher().equals(otherDefaultChain.getRequestMatcher())) {
    +                        throw new UnreachableFilterChainException(
    +                                "The FilterChainProxy contains two filter chains using the matcher "
    +                                        + currentDefaultChain.getRequestMatcher()
    +                                        + ". If you are using multiple <http> namespace "
    +                                        + "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.",
    +                                currentDefaultChain, otherDefaultChain);
    +                    }
                     }
                 }
    -        }
    -        if (chain instanceof DefaultSecurityFilterChain defaultChain) {
    -            filterChain = defaultChain;
             }
         }
     }
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    __

    Why: The current implementation only compares each chain with the previous one, which means duplicate matchers between non-consecutive chains won't be detected. The improved code correctly compares each chain against all subsequent chains, ensuring all duplicates are properly identified, which is critical for security filter chain validation.

    High
    • More

    Copy link

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Hey @GuusArts - I've reviewed your changes - here's some feedback:

    Overall Comments:

    • Consider adding a constructor to UnreachableFilterChainException that only takes a message.
    • The checkForDuplicateMatchers method could be simplified by using a Set to track request matchers.
    Here's what I looked at during the review
    • 🟡 General issues: 2 issues found
    • 🟢 Security: all looks good
    • 🟢 Testing: all looks good
    • 🟢 Complexity: all looks good
    • 🟢 Documentation: all looks good

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Comment on lines +94 to +95
    @Override
    public boolean equals(Object o) {
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    question (bug_risk): Review order sensitivity in equals/hashCode for AndRequestMatcher.

    The equals method compares this.requestMatchers using Objects.equals, which is order-sensitive. Ensure that the ordering of request matchers is significant for equality; otherwise, consider an order-insensitive comparison if the logical contract requires it.

    Comment on lines +85 to +86
    @Override
    public boolean equals(Object o) {
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    question: Evaluate equality semantics for OrRequestMatcher.

    Similar to AndRequestMatcher, the equals implementation here uses an order-sensitive comparison on the requestMatchers list. Confirm that the ordering should affect equality, or consider an alternative approach if order should be ignored.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    2 participants