Skip to content

DenyAllPermissionEvaluator Used As Silent Backup When Two PermissionEvaluator Beans Exist #16872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dcarrol3 opened this issue Apr 3, 2025 · 1 comment
Labels
status: waiting-for-triage An issue we've not yet triaged type: bug A general bug

Comments

@dcarrol3
Copy link

dcarrol3 commented Apr 3, 2025

Describe the bug
When two or more PermissionEvaluator beans are present in an application, Spring seems to silently choose the DenyAllPermissionEvaluator instead of failing to startup due to having duplicate beans.

This feels like a bug, and could have dangerous consequences for enterprises by silently pushing out code to production that seems to build/run as expected, but once an endpoint is hit that calls hasPermission(), it is always denied.

spring-boot team reported it is due to this logic.

To Reproduce
Create two or more PermissionEvaluators in the same project (through an external dependency or otherwise)

Expected behavior
I think Spring should be failing at startup if there are multiple PermissionsEvaluators similar to behavior for other duplicate beans, or otherwise clearly warn developers of the case.

@dcarrol3 dcarrol3 added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Apr 3, 2025
@dcarrol3
Copy link
Author

dcarrol3 commented Apr 3, 2025

Example Code

CustomerPermissionEvaluator1.java

package com.example.demo.security;

import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;

import java.io.Serializable;

public class CustomPermissionEvaluator1 implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        System.out.println("CustomPermissionEvaluator1 was called");
        return true;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return true;
    }
}

CustomerPermissionEvaluator2.java

package com.example.demo.security;

import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;

import java.io.Serializable;

public class CustomPermissionEvaluator2 implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        System.out.println("CustomPermissionEvaluator2 was called");
        return true;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return true;
    }
}

SecurityConfig.java

package com.example.demo.config;

import com.example.demo.security.CustomPermissionEvaluator1;
import com.example.demo.security.CustomPermissionEvaluator2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public PermissionEvaluator permissionEvaluator1() {
        return new CustomPermissionEvaluator1();
    }

    @Bean
    public PermissionEvaluator permissionEvaluator2() {
        return new CustomPermissionEvaluator2();
    }
}

DemoController.java

package com.example.demo;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/test")
    @PreAuthorize("hasPermission(null, 'read')")
    public String test() {
        return "Access granted";
    }
}

To reproduce:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged type: bug A general bug
Projects
None yet
Development

No branches or pull requests

1 participant