Hello, we are migrating to Spring Boot 3 and trying to replace deprecated GlobalMethodSecurityConfiguration, but it doesn't seems to have a clear, straightforward way of doing that. Could you suggest how we should proceed?

Currently we have this (Spring Boot 2.x)

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

    @Autowired
    ApplicationContext applicationContext;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        (1) var expressionHandler = new CustomMethodSecurityExpressionHandler();
        expressionHandler.setApplicationContext(applicationContext);
        return expressionHandler;
    }

    @Override
    protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
       (2) return new CustomPermissionMetadataSource();
    }
}

(1):

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

    private AuthenticationTrustResolver trustResolver =
            new AuthenticationTrustResolverImpl();

    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
            Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root =
                new CustomMethodSecurityExpressionRoot(authentication);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());
        root.setThis(invocation.getThis());
        return root;
    }
}

(2):

public class CustomPermissionMetadataSource extends AbstractFallbackMethodSecurityMetadataSource {

    @Override
    protected Collection findAttributes(Method method, Class<?> targetClass) {
        if (!targetClass.getPackage().getName().startsWith("some.package")) {
            return null;
        }
        List attributes = new ArrayList<>();

        // if the class is annotated as @Controller or @RestController deny access to all methods
        if (AnnotationUtils.findAnnotation(targetClass, Controller.class) != null
                || AnnotationUtils.findAnnotation(targetClass, RestController.class) != null) {
            attributes.add(DENY_ALL_ATTRIBUTE);
        }

        // unless there is a class level annotation
        var classLevelAnnotationPresent =
                AnnotationUtils.findAnnotation(targetClass, PreAuthorize.class) != null ||
                AnnotationUtils.findAnnotation(targetClass, PostAuthorize.class) != null ||
                AnnotationUtils.findAnnotation(targetClass, NoAuthorization.class) != null;

        if (classLevelAnnotationPresent) {
            return null;
        }

        // or method level annotation
        Annotation[] annotations = AnnotationUtils.getAnnotations(method);
        if (annotations != null) {
            for (Annotation a : annotations) {
                if (a instanceof PreAuthorize || a instanceof PostAuthorize || a instanceof NoAuthorization) {
                    return null;
                }
            }
        }
        return attributes;
    }
}

Migrated: (Spring Boot 3.x)

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public Advisor customAuthorize() {
        var pattern = new JdkRegexpMethodPointcut();
        pattern.setPattern("my.package.controller");
        var rule = new CustomAuthorizationManager();
        var interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
        interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() - 1);
        return interceptor;
    }
}
public class CustomAuthorizationManager implements AuthorizationManager<MethodInvocation> {
 @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
      (2) // contains logic defined in CustomPermissionMetadataSource and based on that returns new AuthorizationDecision(true/false)

    }
}
@Component
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

    @Override
    public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
        var context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
        var root = new CustomMethodSecurityExpressionRoot(authentication.get());
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(new AuthenticationTrustResolverImpl());
        root.setRoleHierarchy(getRoleHierarchy());
        root.setThis(mi.getThis());
        context.setRootObject(root);
        return context;
    }

}

What is the goal: (2) To deny access to classes annotated as @ Controller or @ RestController unless there is PreAuthorize, PostAuthorize or NoAuthorization annotation on class/method level. If PreAuthorize or PostAuthorize annotation is present, it is managed by our CustomMethodSecurityExpressionRoot unless it is NoAuthorization (our custom annotation, defining methods as 'authorization not needed') which grants access without further evaluation.

The part where I'm stuck: Which is the correct way to migrate customMethodSecurityMetadataSource - they way I have mentioned above with CustomAuthorizationManager or something else?

Comment From: jzheaux

@YaniM thanks for the report. Sorry, it's quite challenging to go through a post with a lot of unformatted, pasted code, and I'm not clear yet on what you are trying to do and where you are getting stuck.

Can you please put together a minimal GitHub sample that shows where you are stuck?

Also, please consult the migration guide and post here where the guide is unclear.

Comment From: YaniM

@YaniM thanks for the report. Sorry, it's quite challenging to go through a post with a lot of unformatted, pasted code, and I'm not clear yet on what you are trying to do and where you are getting stuck.

Can you please put together a minimal GitHub sample that shows where you are stuck?

Also, please consult the migration guide and post here where the guide is unclear.

Hi, @jzheaux Yes, sorry for the mess. Actually, I had missed something in the migration guide you have posted... Anyway I updated the ticket and pointed out the part where I'm not sure if that's the correct way. Thank you for your time!