3

When updating to Spring Security 6, the JSR250 annotation @RolesAllowed on my @RestController doesn't take the defined roleHierarchy into account.

Related to: AccessDecisionVoter Deprecated with Spring Security 6.x

Since Spring Security 6, the AccessDecisionVoter is deprecated and the suggested way, from the thread above, is to "simply expose a expressionHandler". This didn't work for me with JSR250 enabled.

@Bean
public DefaultMethodSecurityExpressionHandler expressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
    expressionHandler.setRoleHierarchy(roleHierarchy());
    return expressionHandler;
}

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    String hierarchy = "a > b";
    roleHierarchy.setHierarchy(hierarchy);
    return roleHierarchy;
}

It seems like the created AuthorityAuthorizationManager by Jsr250AuthorizationManagerRegistry.resolveManager for RolesAllowed doesn't take the expressionHandler nor DefaultMethodSecurityExpressionHandler into account.

The AuthorityAuthorizationManager does have a field for a roleHierarchy to be set, but I couldn't figure out how or when this is supposed to be called.

I would have commented on the related post above but unfortunately I don't have the required reputation.

cjtfd
  • 31
  • 3
  • You can check my answer in this question for a possible solution: https://stackoverflow.com/questions/74763256/accessdecisionvoter-deprecated-with-spring-security-6-x/75393249#75393249 – Artenes Nogueira Feb 09 '23 at 01:13
  • @ArtenesNogueira I tried your answer and it works only as long as I don't add any JSR250 annotations to my controller. Any idea on how to go about adding the hierarchy to the jsr manager specifically? – cjtfd Feb 09 '23 at 14:58

1 Answers1

2

Unfortunately there is no support to add role hierarchy to JSR250 manager. But there is a workaround that basically clones the library's implementation. At this point it makes more sense to drop JSR250 since you will be just replicating the logic from the libs to your code base, but if you want to do it anyway, just follow these instructions:

  1. Create a custom manager that will deal with the JSR250 annotations:
public class Jsr250CustomAuthorizationManager implements AuthorizationManager<MethodInvocation> {

    static final AuthorizationManager<MethodInvocation> NULL_MANAGER = (a, o) -> null;

    private final RoleHierarchy roleHierarchy;

    public Jsr250CustomAuthorizationManager(RoleHierarchy roleHierarchy) {
        this.roleHierarchy = roleHierarchy;
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
        var manager = resolveManager(methodInvocation);
        return manager.check(authentication, methodInvocation);
    }

    public AuthorizationManager<MethodInvocation> resolveManager(MethodInvocation methodInvocation) {
        if (hasDenyAll(methodInvocation)) {
            return (a, o) -> new AuthorizationDecision(false);
        }

        if (hasPermitAll(methodInvocation)) {
            return (a, o) -> new AuthorizationDecision(true);
        }

        if (hasRolesAllowed(methodInvocation)) {
            var rolesAllowed = (RolesAllowed) methodInvocation.getMethod().getAnnotation(RolesAllowed.class);
            var manager = AuthorityAuthorizationManager.<MethodInvocation>hasAnyRole(rolesAllowed.value());
            manager.setRoleHierarchy(roleHierarchy);
            return manager;
        }

        return NULL_MANAGER;
    }

    public boolean hasDenyAll(MethodInvocation methodInvocation) {
        return methodInvocation.getMethod().getAnnotation(DenyAll.class) != null;
    }

    public boolean hasPermitAll(MethodInvocation methodInvocation) {
        return methodInvocation.getMethod().getAnnotation(PermitAll.class) != null;
    }

    public boolean hasRolesAllowed(MethodInvocation methodInvocation) {
        return methodInvocation.getMethod().getAnnotation(RolesAllowed.class) != null;
    }

}

You can just copy and paste the code above into a new class in your code, this is a class that I made myself based on the one that exists in the spring security lib.

  1. expose this new instance as a bean of type Advisor:
@EnableMethodSecurity(jsr250Enabled = true)
public class MethodSecuritytConfiguration {

    @Bean
    public Advisor jsr250AuthorizationMethodInterceptor() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        var manager = new Jsr250CustomAuthorizationManager(roleHierarchy);
        return AuthorizationManagerBeforeMethodInterceptor.jsr250(manager);
    }

}
  1. In case you are using @EnableMethodSecurity(jsr250Enabled = true), you will also need to add this configuration to your application.properties:
spring.main.allow-bean-definition-overriding=true

This will allow to override a bean that is defined in the security lib that deals with JSR250 annotations. Because this bean's configuration is hardcoded in the lib wihtout exposing a way to change it, we have to override it altogheter to add the behavior we need. Note that the name of your bean needs to be jsr250AuthorizationMethodInterceptor to override the one (with same name) from the security lib. If you remove the jsr250Enabled configuration from EnableMethodSecurity, then you can name your bean anything you want and remove the configuration from application.properties.

Artenes Nogueira
  • 1,422
  • 1
  • 14
  • 23
  • Thanks for the suggestion. I figured that it might not be possible to do without copying code but I asked this question in the hopes to avoid it. – cjtfd Feb 13 '23 at 09:51
  • 1
    @cjtfd yeah, after reading the source code for this feature in the security lib I saw how everything was hardcoded without any option for customization, so the only solution for now is to just override the bean with a custom implementation, which kinda sucks, but it is what it is. – Artenes Nogueira Feb 13 '23 at 21:11