Ben Li
Ben Li

Reputation: 117

PreAuthorized("hasRole()") always return false after upgrade spring security from 3.2 to 4.0.2

After I upgraded spring-security to version 4.0.2 from version 3.2.x, I am able to login to the web application, but I get the access denied if it tries to access a method that has the @PreAuthorized(hasRole()).

I have tried to inject the 'ROLE_' into the GrantAuthorities list, but the result is the same.

The same configuration works fine in version 3.2.x. Can anyone have any idea what did I do wrong?

Thanks

the security-context.xml

<http pattern="/css/**" security="none" />
<http pattern="/Images/**" security="none" />
<http pattern="/javascript/**" security="none" />

<http auto-config='true' use-expressions="true" create-session="always"
    authentication-manager-ref="tunAuthenticationManager">
    <csrf disabled="true" />

    <intercept-url pattern="/new/**"
        access="hasRole('eu_rw') or hasRole('sp_rw')" />
    <intercept-url pattern="/ajax/**"
        access="hasRole('eu_rw') or hasRole('sp_rw')" />
    <intercept-url pattern="/v2/**"
        access="hasRole('eu_rw') or hasRole('sp_rw')" />

    <intercept-url pattern="/monitoring" access="hasRole('sp_rw')" />
    <intercept-url pattern="/monitoring/**" access="hasRole('sp_rw')" />

    <form-login login-page="/logon.jsp" username-parameter="username"
        password-parameter="password" login-processing-url="/j_spring_security_check"
        authentication-failure-url="/logon.jsp?login_error=1"
        default-target-url="/" always-use-default-target="true" />

    <custom-filter position="FIRST" ref="logoutFilter" />
    <custom-filter after="FIRST" ref="requestLoggingFilter" />
    <custom-filter after="LAST" ref="passwordExpirationCheckFilter" />
    <custom-filter after="SWITCH_USER_FILTER" ref="authorizationAdjustmentFilter" />
    <custom-filter after="EXCEPTION_TRANSLATION_FILTER" ref="ajaxTimeoutRedirectFilter" />
</http>



<authentication-manager id="tunAuthenticationManager">
    <authentication-provider ref="strongDaoAuthenticationProviderProxy" />
    <authentication-provider ref="tunAdAuthenticationProvider" />
</authentication-manager>

<beans:bean id="strongDaoAuthenticationProviderProxy"
    class="local.company.tun.security.DaoAuthenticationProviderProxy">
    <beans:constructor-arg>
        <beans:bean id="strongDaoAuthenticationProvider"
            class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
            <beans:property name="userDetailsService" ref="jdbcUserDetailsService" />
            <beans:property name="passwordEncoder" ref="strongEncoder" />
            <beans:property name="saltSource" ref="saltSource" />
        </beans:bean>
    </beans:constructor-arg>
</beans:bean>
<beans:bean id="jdbcUserDetailsService"
    class="local.company.tun.security.tunUserDetailsService">
</beans:bean>
<beans:bean id="authenticationService"
    class="local.company.tun.security.service.impl.AuthenticationServiceImpl">
    <beans:qualifier value="authenticationService" />
    <beans:property name="authenticationManager" ref="tunAuthenticationManager" />
</beans:bean>
<beans:bean
    class="local.company.tun.security.DefaultRolesPrefixPostProcessor" />

servlet-context.xml

<mvc:interceptors>
    <!-- Changes the locale when a 'locale' request parameter is sent;
        e.g. /?locale=de -->
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>

<!-- Activate scanning of @Autowired -->
<context:annotation-config />
<!-- Spring Security - enable pre- post- annotations on Spring managed
    MVC components -->
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" >

</bean>
<security:global-method-security pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler" />
</security:global-method-security>

....

Upvotes: 1

Views: 2042

Answers (3)

Rumpelstiltskin
Rumpelstiltskin

Reputation: 1

You can use hasAuthority() instead of hasRole(), hasAuthority() won't append the defaultRolePrefix. Alternatively, you can also add this to your security-context.xml to override the defaultRolePrefix to be null:

<!-- Remove defaultRolePrefix from SecurityExpressionRoot.java to prevent hasRole() call from appending "ROLE_" to role parameters -->
<bean id="defaultWebSecurityExpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
    <property name="defaultRolePrefix" value=""></property>
</bean>

<bean id="defaultMethodSecurityExpressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="defaultRolePrefix" value=""></property>
</bean>

<security:global-method-security pre-post-annotations="enabled">
    <security:expression-handler ref="defaultMethodSecurityExpressionHandler" />
</security:global-method-security>

Reference article: Why does Spring Security's RoleVoter need a prefix?

Upvotes: 0

Ben Li
Ben Li

Reputation: 117

I am able to resolve the issue by adding a

<beans:bean
class="local.company.tun.security.DefaultRolesPrefixPostProcessor" />

to security-context.xml and the servlet-context.xml. It looks like it needed for both places. the bean in the security-context.xml will prevent adding the "ROLE_" to the the bean in the servlet-context.xml will prevent adding the "ROLE_" to the method level.

for the reference the DefaultRolePrefixPostProcessor.java is copy from the spring security migration 3 -> 4. here is the source code just for the completeness.

public class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
        throws BeansException {

    Logger logger = LoggerFactory.getLogger(DefaultRolesPrefixPostProcessor.class);
    // remove this if you are not using JSR-250
    if(bean instanceof Jsr250MethodSecurityMetadataSource) {
        ((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
    }

    if(bean instanceof DefaultMethodSecurityExpressionHandler) {
        logger.info("overriding the default Role prefix. set it to null ");
        ((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
    }
    if(bean instanceof DefaultWebSecurityExpressionHandler) {
        ((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
    }
    if(bean instanceof SecurityContextHolderAwareRequestFilter) {
        ((SecurityContextHolderAwareRequestFilter)bean).setRolePrefix("");
    }
    return bean;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException {
    return bean;
}

@Override
public int getOrder() {
    return PriorityOrdered.HIGHEST_PRECEDENCE;
}

}

Upvotes: 1

Marek Raki
Marek Raki

Reputation: 3146

You must use ROLE_ prefix in role names and the same in GrantedAuthorities otherwise it won't work unless you have set the prefix as empty.

RoleVoter.setRolePrefix(""); 


@PreAuthorize("hasRole('ROLE_ADMIN')")

new SimpleGrantedAuthority("ROLE_ADMIN")

There is also an idea how to debug. Put a break point in userDetails.getAuthorities() method. Then access the protected resource. Spring will check the actual version of authorities and will be caught. Go up through the stack trace and find the place and values that are compared finding out what is going wrong.

Upvotes: 2

Related Questions