Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security - Url with request parameters rules ignored

I have a web application that uses spring security. It uses <intercept-url ../> elements to describe the access filters for different urls. By default, this does not take request parameters of urls into account. I needed to set custom security rules of an url based on the request parameters. So I've done the following:

1) I created a bean post-processor class that will enable request parameters option for the spring security mechanisms:

<beans:beans>
    . . .
    <beans:bean class="MySecurityBeanPostProcessor">
        <beans:property name="stripQueryStringFromUrls" value="false" />
    </beans:bean>
    . . .
</beans:beans>

And the code:

public class MySecurityBeanPostProcessor implements BeanPostProcessor {

    private Boolean stripQueryStringFromUrls = null;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DefaultFilterInvocationSecurityMetadataSource && stripQueryStringFromUrls != null) {
            ((DefaultFilterInvocationSecurityMetadataSource) bean)
                .setStripQueryStringFromUrls(stripQueryStringFromUrls.booleanValue());
        }
        return bean;
    }

    // code stripped for clarity
}

This should set the spring security metadata source to take into account the request parameters. I have debugged the above code and the stripQueryStringFromUrls property is being set.

2) In my security context xml I have the following definitions:

<intercept-url pattern="/myUrl?param=value" access="!isAuthenticated() or hasRole('ROLE_GUEST')" />
<intercept-url pattern="/myUrl" filters="none" />
...
<intercept-url pattern="/**" access="isAuthenticated()" />

As you can see, I need to access the url with the params specified only if the user is not authenticated, or uses a guest account. Also, I have added a rule for the same url, but without any params, which has no filters.

As far as I know, spring security should be configured providing the more-specific url BEFORE the less-specific, because otherwise the chain will detect the more-general rule first and will not continue to the more-specific. That is why I expect the url with params to be more-specific, therefore to be denied access of authenticated non-guest users. Instead, the defined below more general rule applies. Here is the output:

INFO [STDOUT] 186879 [http-0.0.0.0-8080-1] DEBUG org.springframework.security.web.FilterChainProxy - Candidate is: '/myUrl'; pattern is /myUrl; matched=true

INFO [STDOUT] 186879 [http-0.0.0.0-8080-1] DEBUG org.springframework.security.web.FilterChainProxy - /myUrl?param=value has an empty filter list

I also tried to remove the rule for the url wit no params. Then, instead of getting my rule for the url with params to work, the filter chooses the /** pattern and requires the users to log-in. The output for that is:

INFO [STDOUT] 73066 [http-0.0.0.0-8080-1] DEBUG org.springframework.security.web.FilterChainProxy - Candidate is: '/myUrl'; pattern is /**; matched=true

INFO [STDOUT] 73068 [http-0.0.0.0-8080-1] DEBUG org.springframework.security.web.FilterChainProxy - /myUrl?param=value at position 1 of 8 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'

The application is written in Java 1.6, uses Spring v3.0 and is deployed on JBoss v5.1.0-GA, on a linux machine. I have no clues why the filters behave in the way I described. Your help and advices will be greatly appreciated.


Edit:

As a conclusion, what I observed is that the /myUrl?param=value filter is never applied - as if the entry in the security-context.xml was ignored. This fits the behavior I have observed up to now. I have also tried replacing filters="none" with access="permitAll", switched to regex (and changes patterns accordingly - for example /myUrl?param=value becomes \A/myUrl\?param=value\Z) and in all variations the behavior I get is the same.


Edit 2:

The issue described here is actually invalid. The reasons for this is the following matter: the project where the problem is present has excluded some spring packages due to internal library clashes and incompatibility, while the whole setup somehow worked. I was never made aware of that and actually this unpure configuration renders the whole question obsolete. The concrete reason is that the implementations of isAuthenticated() and isAnonymous() methods were not working as expected, therefore any advice provided here was not working.

like image 870
Ivaylo Slavov Avatar asked Jan 18 '12 09:01

Ivaylo Slavov


1 Answers

In Spring Security 3.0, this is (understandably) a common source of confusion in that the use of filters="none" adds a pattern with an empty list of filters to the FilterChainProxy whereas the use of an access attribute adds a security access rule to the FilterSecurityInterceptor which is used to protect URLs.

The matching process is:

  1. FilterChainProxy matches the request to a filter chain
  2. If the filter chain is non-empty, the request will be checked by the FilterSecurityInterceptor

Both classes maintain a separate ordered list of matchers which they do apply in the order you have defined them, but you need to understand that underneath there are actually two separate beans being configured which are not directly connected.

In a simple namespace application, the <http> block is adding a single filter chain to the FilterChainProxy with pattern /**. Any filters="none" patterns you add will place empty filter chains before the actual chain.

The situation has improved a lot in Spring Security 3.1, in that you configure separate filter chains by using a separate <http> block, which maps more intuitively to what is actually happening at the bean level. The request-matching process has also been improved a lot and now uses a RequestMatcher interface for everything. You can also use this instead of a pattern when configuring the <http> block.

So, your best option is probably to upgrade. You could then implement a RequestMatcher which checks for the presence of the the parameter you are looking for, say MyParamRequestMatcher, and then use:

<http request-matcher-ref="myParamMatcher" security="none" />

<bean:bean id="myParamMatcher" class="MyParamRequestMatcher" />

<http>
    <!-- Define the default filter chain configuration here -->
</http>

Note that matching on parameters using a URL pattern is not very secure in general as it is easy to bypass by reordering the URL, adding bogus patterns and so on. Your case is probably OK, since the version with the parameter is allowing unsecured access and you have patterns which require authentication for other cases.

If you want to stay with 3.0, your best option is to avoid using filters="none" (use isAnonymous() instead) and probably use regular-expression matching rather than ant paths so that you can match the query string more easily. Again I should repeat that rules defined this way can almost certainly be bypassed, so don't rely on them for more security and make sure you are secure by default.


Update:

As a test for my suggestion of using regex matching and permitAll, if I modify the "tutorial" sample application in the 3.0.x branch of Spring Security like so:

<http use-expressions="true" path-type="regex">
    <intercept-url pattern="\A/secure/extreme/.*\Z" access="hasRole('ROLE_SUPERVISOR')"/>
    <intercept-url pattern="\A/secure/index.jsp\?param=value\Z" access="permitAll" />
    <intercept-url pattern="\A/secure/.*\Z" access="isAuthenticated()" />
    <intercept-url pattern="/.*" access="permitAll" />
    ...
</http>

Then I do get the expected behaviour:

[DEBUG,FilterChainProxy] Candidate is: '/secure/index.jsp?param=value'; pattern is /.*; matched=true
[DEBUG,FilterChainProxy] /secure/index.jsp?param=value at position 1 of 12 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
...
[DEBUG,FilterChainProxy] /secure/index.jsp?param=value at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
[DEBUG,FilterChainProxy] /secure/index.jsp?param=value at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
[DEBUG,ExpressionBasedFilterInvocationSecurityMetadataSource] Candidate is: '/secure/index.jsp?param=value'; pattern is \A/secure/extreme/.*\Z; matched=false
[DEBUG,ExpressionBasedFilterInvocationSecurityMetadataSource] Candidate is: '/secure/index.jsp?param=value'; pattern is \A/secure/index.jsp\?param=value\Z; matched=true
[DEBUG,FilterSecurityInterceptor] Secure object: FilterInvocation: URL: /secure/index.jsp?param=value; Attributes: [permitAll]

which shows the FilterChainProxy matching under .* followed by the FilterSecurityInterceptor matching the exact URL with the parameter.

like image 200
Shaun the Sheep Avatar answered Nov 08 '22 18:11

Shaun the Sheep