Summary

I need to customize the behavior of the reactive method security expression handler. In the non reactive version this could be done extending GlobalMethodSecurityConfiguration as stated in the documentation:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        // ... create and return custom MethodSecurityExpressionHandler ...
        return expressionHandler;
    }
}

However, the reactive equivalent ReactiveMethodSecurityConfiguration is a package-private class and can't be extended to modify the framework.

Actual Behavior

We can't modify the expression handler.

Expected Behavior

Allow the definition of a custom MethodSecurityExpressionHandler

Configuration

...

Version

5.2.0.RC1

Comment From: jnfeinstein

The reactive version is bean-based. Is this as simple as adding a conditional?

Comment From: sunildabburi

I had a similar problem. I tried this approach and it worked.

Create the following configuration class that initializes the required security beans along with my custom expression handler bean.

class AuthorizationConfig {

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    DelegatingMethodSecurityMetadataSource methodMetadataSource(
            MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
        var attributeFactory = new ExpressionBasedAnnotationAttributeFactory(methodSecurityExpressionHandler);
        var prePostSource = new PrePostAnnotationSecurityMetadataSource(attributeFactory);
        return new DelegatingMethodSecurityMetadataSource(List.of(prePostSource));
    }

    @Bean
    PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
            MethodSecurityExpressionHandler handler) {
        var postAdvice = new ExpressionBasedPostInvocationAdvice(handler);
        var preAdvice = new ExpressionBasedPreInvocationAdvice();
        preAdvice.setExpressionHandler(handler);
        return new PrePostAdviceReactiveMethodInterceptor(source, preAdvice, postAdvice);
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source) {
        return new MethodSecurityMetadataSourceAdvisor("securityMethodInterceptor", source,
                "methodMetadataSource");
    }

    @Bean
    MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        return new CustomMethodSecurityExpressionHandler(); // my custom expression handler
    }
}

DO NOT use @EnableReactiveMethodSecurity as the above code does what it does but not completely as the above implementation does not take into consideration the import order and default role prefixes which were not required in my case. If you want to use them too, you can clone the code from ReactiveMethodSecurityConfiguration

Hope this helps

Comment From: ssozonoff

@Bean @Primary public DefaultMethodSecurityExpressionHandler getCustomMethodSecurityExpressionHandler() { return new CustomMethodSecurityExpressionHandler(); }

Seems to work as well

Comment From: wickedev

How about avoiding this problem by calling setRoleHierarchy inside ReactiveMethodSecurityConfiguration.methodSecurityExpressionHandler method or adding @ConditionalOnMissingBean annotation?

Comment From: jzheaux

Let's take a look at this after #9401 is merged.

Comment From: LajosPolya

@bean @primary public DefaultMethodSecurityExpressionHandler getCustomMethodSecurityExpressionHandler() { return new CustomMethodSecurityExpressionHandler(); }

Seems to work as well

This worked for me but my CustomMethodSecurityExpressionHandler class has to be annotated with @Component and @Primary

Comment From: jzheaux

Closed in https://github.com/spring-projects/spring-security/pull/15719/commits/60cd8fdc552a340d9b7f7b7085c6e0a7107ecb0b