22

I'm trying to remove the "ROLE_" prefix in Spring Security. The first thing I tried was:

http.servletApi().rolePrefix("");

That didn't work, so I tried creating a BeanPostProcessor as suggested in http://docs.spring.io/spring-security/site/migrate/current/3-to-4/html5/migrate-3-to-4-jc.html#m3to4-role-prefixing-disable. That didn't work either.

Finally, I tried creating my own SecurityExpressionHandler:

  @Override
  protected void configure(HttpSecurity http) throws Exception {
      http
          .authorizeRequests()
          .expressionHandler(webExpressionHandler())
          .antMatchers("/restricted").fullyAuthenticated()
          .antMatchers("/foo").hasRole("mycustomrolename")
          .antMatchers("/**").permitAll();
  }

  private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
      DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
      defaultWebSecurityExpressionHandler.setDefaultRolePrefix("");
      return defaultWebSecurityExpressionHandler;
  }

However, this doesn't work either. If I use "hasAuthority(roleName)" instead of hasRole, it works as expected.

Is it possible to remove the ROLE_ prefix from Spring Security's hasRole check?

Matt Raible
  • 8,187
  • 9
  • 61
  • 120
  • Strange the `BeanPostProcessor` works for me (you did declare it as a `static` bean method and included the `PriorityOrdered` so that it runs very early?) and the same for expression handler. We also have a `DefaultMethodSecurityExpressionHandler`DefaultMethodSecurityExpressionHandler` configured with the prefix set the `null`. – M. Deinum Jul 01 '16 at 06:19
  • Yes, I copied the code for the `BeanPostProcessor ` directly from the documentation. I tried putting the `@Bean` in my `@Configuration` class for Spring Security and in my `@SpringBootApplication` class. I added a `System.out.println` to ensure it's being configured before Spring Security too. `hasAuthority` works as expected, so I guess I'll just use that instead. – Matt Raible Jul 01 '16 at 16:15
  • We have it in a non spring boot application. Could it be that that is interfering or that the security of boot is somehow still configured earlier? – M. Deinum Jul 01 '16 at 20:14

8 Answers8

34

Starting from Spring 4.2, you can define the prefix with a single bean, as described here: https://github.com/spring-projects/spring-security/issues/4134

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

XML version:

<beans:bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
    <beans:constructor-arg value="" />
</beans:bean>
Gustavo Passini
  • 2,348
  • 19
  • 25
Antoine Melki
  • 358
  • 4
  • 7
  • 7
    Didnt Worked for me – KpsLok Oct 18 '17 at 22:34
  • Does not work: see the 4th comment in question https://stackoverflow.com/questions/46756013 – Gaetan Mar 16 '18 at 14:52
  • There is a difference between a GrantedAuthority and a Role. This answer is for GrantedAuthority. – Charles Morin May 28 '18 at 17:34
  • For those who have exception with annotation: move the @Bean instance from the security config to the MVC config Ref: https://stackoverflow.com/questions/48971937/ugrade-spring-boot-2-0-0-rc2-exception-no-servletcontext-set – Ismail Yavuz Apr 28 '19 at 13:15
  • 1
    It works with `HttpServletRequest.isUserInRole(String)` but not works with `@Secured(String)`. – mojtab23 Dec 17 '19 at 12:28
  • It works with JSR250's `@RolesAllowed` (which is said to be superior over springs' own `@Secured` annoatation here: https://stackoverflow.com/a/57394049/913093) – denu Jul 21 '22 at 13:40
8

The following configuration works for me.

@Override
public void configure(WebSecurity web) throws Exception {
    web.expressionHandler(new DefaultWebSecurityExpressionHandler() {
        @Override
        protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
            WebSecurityExpressionRoot root = (WebSecurityExpressionRoot) super.createSecurityExpressionRoot(authentication, fi);
            root.setDefaultRolePrefix(""); //remove the prefix ROLE_
            return root;
        }
    });
}
walsh
  • 3,041
  • 2
  • 16
  • 31
7

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

The RoleVoter.rolePrefix is what is used for @Secured("ADMIN") style of method security.

So along with the GrantedAuthorityDefaults, I had to also add this CustomGlobalMethodSecurity class to override the defaults for RoleVoter.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class CustomGlobalMethodSecurity extends GlobalMethodSecurityConfiguration {

    protected AccessDecisionManager accessDecisionManager() {
        AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();

        //Remove the ROLE_ prefix from RoleVoter for @Secured and hasRole checks on methods
        accessDecisionManager.getDecisionVoters().stream()
                .filter(RoleVoter.class::isInstance)
                .map(RoleVoter.class::cast)
                .forEach(it -> it.setRolePrefix(""));

        return accessDecisionManager;
    }
}
Clijsters
  • 4,031
  • 1
  • 27
  • 37
Jeff Sheets
  • 1,181
  • 12
  • 18
  • I use the @EnableGlobalMethodSecurity in WebSecurityConfigurerAdapter, how to get access to AccessDecisionManager? – Ninja Dec 26 '18 at 12:26
  • @Ninja The AccessDecisionManager will have to be configured in an additional class, as I don't believe it can be done from the WebSecurityConfigurerAdapter. I think you'll just need to create a new Configuration class that extends GlobalMethodSecurityConfiguration – Jeff Sheets Dec 27 '18 at 13:48
  • 1
    But that only helps if you use `@Secured` annotation because `@Secured` values are evaluated in `RoleVoter` class, and `@PreAuthorize` annotation expression evaluated in `PreInvocationAuthorizationAdviceVoter` class, publishing another bean `new GrantedAuthorityDefaults("")` with prefix argument in constructor which also affect on `@PreAuthorize`. – Yougeshwar Khatri May 22 '19 at 11:49
5

If you use Spring Boot 2, you can create this bean to override the RoteVoter prefix

@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return  new GrantedAuthorityDefaults("<anything you want>");
}

It works because when GlobalMethodSecurityConfiguration creates AccessDecisionManager in the method GlobalMethodSecurityConfiguration.accessDecisionManager(). Here is the snippet of code, notice the null check on grantedAuthorityDefaults

    protected AccessDecisionManager accessDecisionManager() {
    ....
    RoleVoter roleVoter = new RoleVoter();
    GrantedAuthorityDefaults grantedAuthorityDefaults =
            getSingleBeanOrNull(GrantedAuthorityDefaults.class);
    if (grantedAuthorityDefaults != null) {
        roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
    }
    decisionVoters.add(roleVoter);
    decisionVoters.add(new AuthenticatedVoter());
    return new AffirmativeBased(decisionVoters);
}
Hung Tran
  • 1,595
  • 2
  • 17
  • 17
4

If you are prior to 4.2 and are using so called voters (you are if you use annotations like @hasRole etc) then you need to define below beans in the context:

@Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
    defaultMethodSecurityExpressionHandler.setDefaultRolePrefix("");
    return defaultMethodSecurityExpressionHandler;
}

@Bean
public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
    DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
    defaultWebSecurityExpressionHandler.setDefaultRolePrefix("");
    return defaultWebSecurityExpressionHandler;
}

These beans are used to create evaluation context for spell expressions and they have a defaultRolePrefix set to 'ROLE_'. Although it depends on your use case. This one worked for me and above didn't.

EDIT: answering question about xml configuration -> of course it can be done in xml. Everything done in java config can be written in xml configuration. Here is example (although I did not test it so there might be a typo or something):

<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>
Piotr Tempes
  • 1,091
  • 1
  • 12
  • 26
  • Is there any equivalent in XML configuration? – Mostafa Barmshory Jul 02 '17 at 12:34
  • I have updated my answer with xml config... but as I wrote, there might be some kind of a mistake - I did not tested it out. The general rule is - everything in java config can be done in xml as well. – Piotr Tempes Jul 03 '17 at 11:49
  • fails in my JHipster application right during boot, with `org.springframework.beans.factory.BeanCreationException: .......; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling ` – Petr Kozelka Dec 20 '17 at 07:15
  • Petr - it's just a wild guess, but if you get this error it probably means you do not have a web context so you do not need the second bean - DefaultWebSecurityExpressionHandler. Anyway each configuration is different, so it is hard to tell what the problem really is in your case guys. For me these two beans fixed the problem. – Piotr Tempes Dec 20 '17 at 20:19
0

I post summary working solutions for me:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    /**
     * Allow skip ROLE_ when check permission using @Secured, like:
     *  @Secured({AuthorityConstants.ROLE_SYSTEM_ADMIN})
     */
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();
        setAuthorityRolePrefix(accessDecisionManager, "");
        return accessDecisionManager;
    }

    private void setAuthorityRolePrefix(AffirmativeBased accessDecisionManager, String rolePrefix) {
        accessDecisionManager.getDecisionVoters().stream()
                .filter(RoleVoter.class::isInstance)
                .map(RoleVoter.class::cast)
                .forEach(it -> it.setRolePrefix(rolePrefix));
    }

    /**
     * Allow skip ROLE_ when check permission using @PreAuthorize, like:
     * @PreAuthorize("hasAnyRole('USER', 'SYSTEM_ADMIN')")
     */
    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }
}
0

With Spring Boot 2.3 I got this exception at boot time:

Error creating bean with name 'resourceHandlerMapping' defined in class path resource 
[org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: 
Factory method 'resourceHandlerMapping' threw exception;
nested exception is java.lang.IllegalStateException: No ServletContext set

Here is my solution:

@Configuration
@Import(RolePrefixConfiguration.class)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

  public static class RolePrefixConfiguration {

    @Bean
    public GrantedAuthorityDefaults grantedAuthorityDefaults() {
      log.debug("remove prefix 'ROLE_' from grantedAuthorityDefaults");
      return new GrantedAuthorityDefaults("");
    }

  }
   
   // ... your usual config 
}
IPP Nerd
  • 992
  • 9
  • 25
0

This worked for me.

public SimpleAuthorityMapper grantedAuthority()                             
{     
    SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();       
    mapper.setPrefix("");      
    return mapper;      
}

@Override   
public void configure(AuthenticationManagerBuilder auth)  
{  
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthority());
auth.authenticationProvider(keycloakAuthenticationProvider);   
}