springspring-securityhttp-status-code-403

Spring Security Migration 6.1.4: Facing Issue While Using hasAnyRole() in Spring Security XML Configuration


M currently working on migrating an application to JDK 17, Apache Tomcat 10.1 and Spring Framework 6.0. As part of this migration, Spring Security framework has been migrated to 6.1.4. After completing the necessary code and configuration changes, the start of Tomcat application server was successful and was able to login into application as well. However, when I try to access a particular web page from within the application it gives HTTP response of 403.

Following are some of the details obtained after analyzing the issue further. It is a GET request with the following URL:
<base_url>/SwiftWeb/pages/client/client_tab.jsf?sml_current_id=51828371
I checked the Spring Security configuration file (applicationContext-security.xml) and observed that for this web page following is the security configuration:

<security:intercept-url pattern="/pages/client/**"
            access="hasAnyRole('BROKERPERSON, BROKERPERSONVIEW, PROCESSOWNER')" />

Following is the snippet of the Spring Security configuration file where the roles for the different pages have been configured:

<security:http auto-config="true" use-expressions="true" disable-url-rewriting="true">
        <security:form-login login-page="/pages/login.jsf"/>    
        <security:custom-filter position="PRE_AUTH_FILTER"
            ref="preAuthFilter" />
        <security:intercept-url pattern="/pages/client/**"
            access="hasAnyRole('BROKERPERSON, BROKERPERSONVIEW, PROCESSOWNER')" />
        <security:intercept-url pattern="/pages/contacts/**"
            access="hasAnyRole('BROKERPERSON, COMPANYADMIN, BRANCHADMIN, PROCESSOWNER, ACCOUNTANT, PREMIUMFUNDINGUSER, SYSTEM')" />
        <security:intercept-url pattern="/pages/report/**"
            access="permitAll" />
        <security:intercept-url pattern="/pages/home/**"
            access="permitAll" />
        <security:intercept-url pattern="/**/*.jsf" access="permitAll" />
        <security:intercept-url pattern="/**/*.gif" access="permitAll" />
        <security:intercept-url pattern="/**/*.js" access="permitAll" />
        <security:intercept-url pattern="/pages/security/password_update.jsf"
            access="hasAnyRole('ROLE_ANONYMOUS, BROKERPERSON, COMPANYADMIN, BRANCHADMIN, PROCESSOWNER, ACCOUNTANT, PREMIUMFUNDINGUSER, SYSTEM')" /> 
        <security:csrf disabled="true"/>
        <security:headers  >
            <security:frame-options policy="SAMEORIGIN" /> 
            <security:hsts disabled="true"/>
            <security:content-type-options disabled="true"/>
            <security:xss-protection disabled="true"/>
            <security:cache-control disabled="true"/>
        </security:headers>
                
    </security:http>
    

    <!-- User Context bean defined as session scope using aop scoped proxy -->
    <bean id="userContext" class="com.swift.core.security.view.UserContext"
        scope="session">
        <aop:scoped-proxy proxy-target-class="true" />
    </bean>

    <!-- List of request handler(s) beans -->
    <bean id="verifyUserHandler"
        class="com.swift.core.security.common.handlers.VerifyUserRequestHandler" />
    <bean id="userAuthHandler"
        class="com.swift.core.security.common.handlers.UserAuthenticationRequestHandler" />
    <bean id="userAuthDummyHandler"
        class="com.swift.core.security.common.handlers.UserAuthenticationRequestDummyHandler" />
    <bean id="userParamHandler"
        class="com.swift.core.security.common.handlers.UserParameterRequestHandler" />
    <bean id="userWebSecurityExpressionHandler" 
        class="com.swift.core.security.common.handlers.UserWebSecurityExpressionHandler" />

    <bean id="preAuthFilter" class="com.swift.core.security.filter.SwiftPreAuthFilter">
        <property name="checkForPrincipalChanges">
        <value>true</value>
        </property>
        <property name="handlers">
            <!-- List of request handler(s) -->
            <list>
                <ref bean="verifyUserHandler" />
                <ref bean="userAuthHandler" />
            <!--             
                <ref bean="userAuthDummyHandler" />
             -->    
                <ref bean="userParamHandler" />
                <ref bean="userWebSecurityExpressionHandler" />
            </list>
        </property>
        <property name="authenticationManager" ref="authenticationManager" />
    </bean>
    
    <bean id="preauthAuthProvider"
        class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="preAuthenticatedUserDetailsService">
            <bean id="userDetailsServiceWrapper"
                class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
                <property name="userDetailsService" ref="preAuthUserDetailsService" />
            </bean>
        </property>
    </bean>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider
            ref="preauthAuthProvider">
        </security:authentication-provider>
    </security:authentication-manager>

On further debugging the java code observed that, while trying to access this page, User Context has roles as BROKERPERSON and PROCESSOWNER and authorities as ROLE_BROKERPERSON and ROLE_PROCESSOWNER. Therefore, as per my analysis the back-end code has the required user roles and authorities when tried to access the page, still 403 response code is being observed.This issue is being observed only for the url pattern where the access mentions specific roles. For all those URL where access is permitAll its is working fine. Furthermore, same code was working properly for the older version i.e. JDK 8, Apache Tomcat 8.5, Spring Framework 4.2.x and Spring Security 4.0.x.

As a workaround, I have changed the highlighted configuration from hasAnyRole to hasRole like below:

<security:intercept-url pattern="/pages/client/**"
            access="hasRole('BROKERPERSON')" />
<security:intercept-url pattern="/pages/client/**"
            access="hasRole('BROKERPERSONVIEW')" />
<security:intercept-url pattern="/pages/client/**"
            access="hasRole('PROCESSOWNER')" />

After this change the web page is working fine as expected and no 403 response code is being observed. However, question still remains the same as to why 403 response code is obtained when hasAnyRole is used. I tried to find answers on technical forum but didn't saw this type of issue being reported before.Do let me know if someone did faced similar kind of issue before and steps taken to resolve it?


Solution

  • Consider using hasAnyRole('BROKERPERSON', 'BROKERPERSONVIEW', 'PROCESSOWNER'). Ensure that each role is separated by a comma.

    For further details, please consult the documentation at https://docs.spring.io/spring-security/reference/5.7/servlet/authorization/expression-based.html.