Summary

Since 4.2, we can control the default role prefix(default is ROLE_) at the single point of definition (gh-3701). However, it not apply to access control definitions using the ExpressionInterceptUrlRegistry#hasRole().

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ...
        http.authorizeRequests()
                .anyRequest().hasRole("USER"); // Allow access from user granted USER role
    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(
                User.withUsername("kazuki43zoo")
                        .password("password")
                        .authorities("USER")  // Grant the USER role (without ROLE_ prefix)
                        .build());
        return userDetailsManager;
    }

}

Actual Behavior

If login with the kazuki43zoo, 403 Forbidden error has been occurred.

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Wed Nov 16 01:06:38 JST 2016
There was an unexpected error (type=Forbidden, status=403).
Access is denied

Expected Behavior

Can access the specified resource after authentication success. (200 OK)

Version

4.2.0

Workaround

It work as following configuration instead of.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ...
        http.authorizeRequests()
                .anyRequest().access("hasRole('USER')"); // Use the access() instead of hasRole()
    }

Comment From: kazuki43zoo

Related with this, the UserBuilder#roles method append the ROLE_ prefix to granted authority. Is this behavior works as designed ?

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(
                User.withUsername("kazuki43zoo")
                        .password("password")
                        .roles("USER") // has been append the ROLE_ prefix
//                        .authorities("USER")
                        .build());
        return userDetailsManager;
    }

In this case, we can use the authorities method instead of.

Comment From: tomas-silhavy

I can confirm that it's not working in version 4.2.3. The problem lies here

ExpressionUrlAuthorizationConfigurer.hasRole(role)

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

private static String hasRole(String role) {
        Assert.notNull(role, "role cannot be null");
        if(role.startsWith("ROLE_")) {
            throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
        } else {
            return "hasRole('ROLE_" + role + "')";
        }
    }

and same constant string "ROLE_" is on several places in this class.

Comment From: jeffsheets

Possibly related to this: https://stackoverflow.com/a/46817507/1469525 Or maybe completely separate.

GrantedAuthorityDefaults will change the prefix for the DefaultWebSecurityExpressionHandler and the DefaultMethodSecurityExpressionHandler, but it doesn't modify the RoleVoter.rolePrefix that is setup from @EnableGlobalMethodSecurity.

It would be nice if the RoleVoter also could get the rolePrefix from GrantedAuthorityDefaults

Comment From: BenDol

protected AccessDecisionManager accessDecisionManager() { inside GlobalMethodSecurityConfiguration this is simply calling new RoleVoter() rather than using the Bean factory that has the proper prefix configuration.