How do I remove the ROLE_ prefix from Spring Security with JavaConfig?

24,723

Solution 1

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>

Solution 2

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;
        }
    });
}

Solution 3

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;
    }
}

Solution 4

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);
}

Solution 5

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>
Share:
24,723

Related videos on Youtube

Matt Raible
Author by

Matt Raible

Web developer and Java Champion that loves to architect and build slick-looking UIs using CSS and JavaScript. When he's not evangelizing Okta and open source, he likes to ski with his family, drive his VWs, and enjoy craft beer.

Updated on September 29, 2020

Comments

  • Matt Raible
    Matt Raible over 3 years

    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?

    • M. Deinum
      M. Deinum almost 8 years
      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 DefaultMethodSecurityExpressionHandlerDefaultMethodSecurityE‌​xpressionHandler` configured with the prefix set the null.
    • Matt Raible
      Matt Raible almost 8 years
      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.
    • M. Deinum
      M. Deinum almost 8 years
      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?
  • Mostafa Barmshory
    Mostafa Barmshory almost 7 years
    Is there any equivalent in XML configuration?
  • Piotr Tempes
    Piotr Tempes almost 7 years
    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.
  • Brenno Leal
    Brenno Leal over 6 years
    Didnt Worked for me
  • Petr Kozelka
    Petr Kozelka over 6 years
    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
  • Piotr Tempes
    Piotr Tempes over 6 years
    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.
  • Gaetan
    Gaetan about 6 years
    Does not work: see the 4th comment in question stackoverflow.com/questions/46756013
  • Charles Morin
    Charles Morin almost 6 years
    There is a difference between a GrantedAuthority and a Role. This answer is for GrantedAuthority.
  • Ninja
    Ninja over 5 years
    I use the @EnableGlobalMethodSecurity in WebSecurityConfigurerAdapter, how to get access to AccessDecisionManager?
  • Jeff Sheets
    Jeff Sheets over 5 years
    @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
  • Ismail Yavuz
    Ismail Yavuz about 5 years
    For those who have exception with annotation: move the @Bean instance from the security config to the MVC config Ref: stackoverflow.com/questions/48971937/…
  • Yougesh
    Yougesh almost 5 years
    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.
  • mojtab23
    mojtab23 over 4 years
    It works with HttpServletRequest.isUserInRole(String) but not works with @Secured(String).