SoFunction
Updated on 2025-04-14

Implementation of SpringSecurity Authorization Mechanism (AccessDecisionManager and Voting Decision)

introduction

In enterprise-level application development, security control not only includes authentication—confirmation of user identity, but also authorization—determines whether the user has the right to perform specific operations. Spring Security provides a carefully designed authorization mechanism at the heart of which is AccessDecisionManager and voting system. This mechanism provides finer granularity and more flexible access control capabilities compared to simple role checks. This article will explore the internal workings of the Spring Security authorization framework in depth, focusing on analyzing how AccessDecisionManager makes authorization decisions through the voting mechanism and how to customize configurations based on business needs. Through this knowledge, developers can build access control systems that are both secure and flexible.

1. Spring Security Authorization Architecture

Spring Security's authorization architecture adopts a combination of responsibility chains and voting models to make the authorization decision process modular and scalable. The authorization process begins with the FilterSecurityInterceptor in SecurityFilterChain, which intercepts requests for protected resources, collects secure metadata (such as required permissions), and then delegates to AccessDecisionManager for authorization judgment. AccessDecisionManager implements complex authorization strategies by combining multiple AccessDecisionVoters, each voter votes for, against or abstains to authorization requests based on his or her own logic. This hierarchical design allows the authorization logic to be completely separated from the business code, making it easier to maintain and expand.

// FilterSecurityInterceptor Key Partspublic class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
    
    private final FilterInvocationSecurityMetadataSource securityMetadataSource;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        // Encapsulate HTTP requests        FilterInvocation fi = new FilterInvocation(request, response, chain);
        
        // Conduct security interception        invoke(fi);
    }
    
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        // Check whether the security interceptor should be applied        if ((() != null) && (().getAttribute(FILTER_APPLIED) != null)) {
            // The security interceptor has been applied, continue to execute the filter chain            ().doFilter((), ());
            return;
        }
        
        // Mark interceptor has been applied        ().setAttribute(FILTER_APPLIED, );
        
        // Perform security interception        InterceptorStatusToken token = (fi);
        
        try {
            // Continue to execute the filter chain            ().doFilter((), ());
        } finally {
            // Clean up security context            (token);
        }
        
        // Execute post-processing        (token, null);
    }
    
    @Override
    public Class<?> getSecureObjectClass() {
        return ;
    }
    
    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return ;
    }
}

2. AccessDecisionManager interface design

AccessDecisionManager is the core interface of Spring Security authorization system, responsible for coordinating multiple AccessDecisionVoters and making final authorization decisions. It defines three key methods: decision() performs authorization judgment, support (ConfigAttribute) checks whether specific configuration attributes are supported, and support (Class) checks whether specific security object types are supported. Through these methods, AccessDecisionManager can flexibly handle different types of authorization requests, such as web requests, method calls, or access to specific domain objects. Spring Security provides three built-in implementations: AffirmativeBased (passed as long as one vote is accepted), ConsensusBased (majority decision), and UnanimousBased (requires unanimous votes).

// AccessDecisionManager interface definitionpublic interface AccessDecisionManager {
    /**
      * Make access control decisions for a given security object
      * @param authentication The authentication information of the current user
      * @param object The secure object to access
      * @param configAttributes Security configuration properties of security objects
      * @throws AccessDeniedException If access is denied
      * @throws InsufficientAuthenticationException if authentication is insufficient
      */
    void decide(Authentication authentication, Object object, 
            Collection<ConfigAttribute> configAttributes) 
            throws AccessDeniedException, InsufficientAuthenticationException;
    
    /**
      * Check whether this AccessDecisionManager supports the specified ConfigAttribute
      * @param attribute Configuration attribute to check
      * @return Return true if this property is supported
      */
    boolean supports(ConfigAttribute attribute);
    
    /**
      * Check whether this AccessDecisionManager supports the specified security object type
      * @param clazz Type of secure object
      * @return Return true if this type is supported
      */
    boolean supports(Class<?> clazz);
}

3. Voting decision-making is realized

Spring Security provides three different AccessDecisionManager implementations, each representing a different voting strategy. AffirmativeBased adopts a "one-vote pass" strategy, allowing access as long as one voter votes for it, which is the loosest strategy. ConsensusBased is based on the principle of "majority" and makes decisions based on the comparison results of a vote of favor and a vote of objection. UnanimousBased requires all voters to vote in favor (or abstain) before access is allowed, which is the strictest strategy. These three implementations cover different security needs from loose to strict, and developers can choose appropriate strategies based on business scenarios.

// AffirmativeBased implements key codepublic class AffirmativeBased extends AbstractAccessDecisionManager {
    
    public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
        super(decisionVoters);
    }
    
    @Override
    public void decide(Authentication authentication, Object object, 
            Collection<ConfigAttribute> attributes) 
            throws AccessDeniedException {
        int deny = 0;
        
        // traverse all voters        for (AccessDecisionVoter voter : getDecisionVoters()) {
            // Get voting results            int result = (authentication, object, attributes);
            
            switch (result) {
            case AccessDecisionVoter.ACCESS_GRANTED: // Vote passed                return; // Return immediately to allow access            case AccessDecisionVoter.ACCESS_DENIED: // Vote rejection                deny++; // Count the rejection votes                break;
            default: // Abstain                break;
            }
        }
        
        // If there is a rejection ticket and no pass, access is denied        if (deny > 0) {
            throw new AccessDeniedException("Access denied, security vote failed");
        }
        
        // All voters abstain from the configuration decision (allowed by default)        if (getAllowIfAllAbstainDecisions()) {
            return;
        }
        
        // Access is denied by default        throw new AccessDeniedException("Access denied, no voter agrees");
    }
}

4. AccessDecisionVoter mechanism

AccessDecisionVoter is a voter role in the Spring Security authorization mechanism, responsible for voting for a specific authorization request. Each voter implements the vote() method, returning ACCESS_GRANTED (for), ACCESS_DENIED (opposite), or ACCESS_ABSTAIN (abstention). Commonly used voters include: RoleVoter (role-based voting), AuthenticatedVoter (authenticated status voting), and WebExpressionVoter (SpEL expression voting). The flexibility of the voter is that it can make decisions based on any condition, not only limited to user roles, but also consider factors such as time, location, resource attributes, etc.

// AccessDecisionVoter interfacepublic interface AccessDecisionVoter<S> {
    /**
      * Constants that favor access
      */
    int ACCESS_GRANTED = 1;
    
    /**
      * Access denied constant
      */
    int ACCESS_DENIED = -1;
    
    /**
      * Abstaining constant
      */
    int ACCESS_ABSTAIN = 0;
    
    /**
      * Voting for access requests
      * @param authentication The authentication information of the current user
      * @param object The secure object to access
      * @param attributes Configuration properties of security objects
      * @return Voting results: ACCESS_GRANTED, ACCESS_DENIED or ACCESS_ABSTAIN
      */
    int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
    
    /**
      * Check whether this voter supports the specified configuration properties
      * @param attribute Configuration attribute to check
      * @return Return true if this property is supported
      */
    boolean supports(ConfigAttribute attribute);
    
    /**
      * Check whether this voter supports the specified security object type
      * @param clazz Type of secure object
      * @return Return true if this type is supported
      */
    boolean supports(Class<?> clazz);
}

// RoleVoter implementationpublic class RoleVoter implements AccessDecisionVoter<Object> {
    
    private String rolePrefix = "ROLE_";
    
    public int vote(Authentication authentication, Object object, 
            Collection<ConfigAttribute> attributes) {
        // If there is no attribute, abstain        if (()) {
            return ACCESS_ABSTAIN;
        }
        
        // Get user permissions        Collection<? extends GrantedAuthority> authorities = 
                ();
        
        // Check each configuration property        for (ConfigAttribute attribute : attributes) {
            if ((attribute)) {
                // Attribute value as required role                String role = ();
                
                // Check whether the user has the role                for (GrantedAuthority authority : authorities) {
                    if ((())) {
                        return ACCESS_GRANTED; // The user has the required roles, allowing access                    }
                }
                
                // Let's go here and indicate that the user does not have the required role, return to reject                return ACCESS_DENIED;
            }
        }
        
        // No supporting attributes, abstain        return ACCESS_ABSTAIN;
    }
    
    @Override
    public boolean supports(ConfigAttribute attribute) {
        // Check if the attribute starts with a role prefix        return (() != null) && 
                ().startsWith(rolePrefix);
    }
    
    @Override
    public boolean supports(Class<?> clazz) {
        return true; // Support all types of security objects    }
}

5. Custom access control rules

The biggest advantage of Spring Security's authorization mechanism is its scalability, and developers can easily implement custom access control rules. By creating a custom AccessDecisionVoter, authorization decisions can be made based on business-specific logic, such as restricting access for a specific period of time, controlling permissions based on user attributes, or implementing data row-level security. Custom voters simply implement three methods of AccessDecisionVoter interface and add them to the list of voters in AccessDecisionManager. This approach makes complex authorization requirements easy to implement and maintain.

// Custom working hours voters, only allowed to access during working hourspublic class BusinessHoursVoter implements AccessDecisionVoter<Object> {
    
    private final int startHour = 9;  // Work start time    private final int endHour = 17;   // Work end time    
    @Override
    public int vote(Authentication authentication, Object object, 
            Collection<ConfigAttribute> attributes) {
        // Check if there are any working time limit attributes        boolean businessHoursRequired = ()
                .anyMatch(a -> "BUSINESS_HOURS_ONLY".equals(()));
        
        // If there is no time limit, abstain        if (!businessHoursRequired) {
            return ACCESS_ABSTAIN;
        }
        
        // Get the current time        LocalTime now = ();
        int currentHour = ();
        
        // Check if it is during working hours        if (currentHour >= startHour && currentHour < endHour) {
            return ACCESS_GRANTED;
        } else {
            return ACCESS_DENIED;
        }
    }
    
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return attribute != null && "BUSINESS_HOURS_ONLY".equals(());
    }
    
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

// Custom configuration@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/admin/**").access("hasRole('ADMIN') and hasAuthority('BUSINESS_HOURS_ONLY')")
                .anyRequest().authenticated()
            .and()
                .formLogin()
                .loginPage("/login").permitAll();
        
        // Replace the default AccessDecisionManager        ()
                .accessDecisionManager(accessDecisionManager());
    }
    
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<?>> voters = new ArrayList<>();
        (new WebExpressionVoter());
        (new RoleVoter());
        (new AuthenticatedVoter());
        (new BusinessHoursVoter());  // Add custom voter        
        // Use the "one vote" strategy        return new AffirmativeBased(voters);
    }
}

VI. Method-level safety control

In addition to authorization control for web requests, Spring Security also provides method-level security control, allowing developers to apply authorization rules directly on business methods. Through @PreAuthorize, @PostAuthorize and other annotations, you can use SpEL expressions to define complex access conditions. These annotations are processed by MethodSecurityInterceptor, which also uses AccessDecisionManager to make authorization decisions. Method-level security shares the same authorization architecture as Web security, but provides a more granular control granularity, which is particularly suitable for permission management at the business logic layer.

// Enable method level security@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        // Custom method-level secure AccessDecisionManager        AffirmativeBased accessDecisionManager = (AffirmativeBased) ();
        
        // Get existing voters and add custom voters        List<AccessDecisionVoter<?>> voters = new ArrayList<>(
                ());
        (new BusinessHoursVoter());
        
        // Create a new AccessDecisionManager        return new AffirmativeBased(voters);
    }
}

// Use method-level security in service class@Service
public class UserService {
    
    @PreAuthorize("hasRole('ADMIN') and hasAuthority('BUSINESS_HOURS_ONLY')")
    public void deleteUser(Long userId) {
        // Delete user logic    }
    
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public UserDetails viewUserProfile(Long userId) {
        // View user profile logic        return userProfile;
    }
    
    @PostAuthorize(" ==  or hasRole('ADMIN')")
    public UserDetails loadUserById(Long userId) {
        // Only allow viewing of your own details or administrators to view        return userDetails;
    }
}

Summarize

Spring Security's authorization mechanism is centered on AccessDecisionManager and voting decision-making system, providing a flexible and powerful access control framework. Through the combination of the responsibility chain model and the policy model, it realizes the modularity and scalability of the authorization logic. AccessDecisionManager coordinates multiple AccessDecisionVoters to make final authorization decisions based on different voting strategies, supporting a variety of security needs from loose to strict. Built-in voters such as RoleVoter and WebExpressionVoter satisfy basic authorization scenarios, while custom voters enable complex business rules to be implemented. Method-level security control further expands authorization capabilities, allowing developers to apply granular permission management at the business method level. By understanding and mastering this authorization mechanism, developers can build both safe and flexible enterprise-level applications to effectively balance security needs and user experience. In today's increasingly complex security threats, Spring Security's authorization framework provides developers with powerful tools to deal with challenges, making complex authorization logic clear and maintainable, and laying a solid foundation for the security foundation of application systems.

This is the article about the implementation of SpringSecurity authorization mechanism (AccessDecisionManager and voting decisions). For more relevant SpringSecurity authorization mechanism content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!