Skip to content

Limited customization with AuthorizationManager #13060

Open
@YaniM

Description

@YaniM

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: coreAn issue in spring-security-core

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions