Custom Access Decision Voter

In this article, we will look at the Custom Access Decision Voter in Spring Security. We will look at the steps and configurations to create Spring Security custom access decision voter.

Custom Access Decision Voter

Spring security is a robust framework and comes with a lot of default which are sufficient for most the use cases. There are some cases where we may need to implement our own custom access decision voter. The Spring security flexible architecture makes it easy to implement and configure decision voter. This article is part of our Spring security tutorial and the source code for complete series is available on our GitHub repository.

1. Scenario

To show the Spring security custom access decision voter, we’ll implement a use case where we will not authorize the request if the username contains “anonymous” words. Our custom access voter will deny authorization request.

2. AccessDecisionVoter

To create the Spring security custom access decision voter, we will create a new decision voter by implementing the AccessDecisionVoter interface. This is how the interface look like:

public interface AccessDecisionVoter<S> {
	
    int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;

	
	/**
	 * Indicates whether this {@code AccessDecisionVoter} is able to vote on the passed
	 * {@code ConfigAttribute}.
	 */
	boolean supports(ConfigAttribute attribute);

	/**
	 * Indicates whether the {@code AccessDecisionVoter} implementation is able to provide
	 * access control votes for the indicated secured object type.
	 *
	 * @param clazz the class that is being queried
	 *
	 * @return true if the implementation can process the indicated class
	 */
	boolean supports(Class<?> clazz);

	
	int vote(Authentication authentication, S object,
			Collection<ConfigAttribute> attributes);
}
  • support() – This method shows if the given DecisionVoter can vote or not.
  • <strong>vote()</strong> – shows whether access is granted.

Keep in mind that Spring security provides and use several AccessDecisionVoter implementations. We will also use those implementations along with our custom DecisionVoter. For complete list, read Spring security java-docs. Here are some important for your refrence.

DecisionVoter NameDescription
WebExpressionVoterVoter which handles web authorization decisions.This enable us to use SpEL.
e.g .antMatchers("/account/**").hasAuthority("CUSTOMER")
AuthenticationVoterCast a vote based on the Authentication object’s level of authentication – specifically looking for either a fully authenticated pricipal, one authenticated with remember-me or, finally, anonymous.
RoleVoterVotes if any of the configuration attributes starts with the String “ROLE_”.

3. Custom AcessDecisionVoter Implementation

Let’s create our Spring security custom AcessDecisionVoter by implementing the AcessDecisionVoter interface. This is how the code looks like:

package com.javadevjournal.core.security.voter;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class CustomAccessDecisionVoter implements AccessDecisionVoter<Object> {

    private String anonymousUserName ="anonymous";

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {

        final String username = (authentication.getPrincipal() == null ) ? "NONE_PROVIDED" : authentication.getName();
        if(StringUtils.equalsAnyIgnoreCase(username, anonymousUserName)){
            return ACCESS_DENIED;
        }
        return ACCESS_GRANTED;
    }
}

Let’s try to understand some of the important aspects of the custom authentication voter. The main logic is under the voter method which check if the username is equal to our configured anonymousUserName, if yes, it will return ACCESS_DENIED. For the other cases, our decision voter will return ACCESS_GRANTED. In case we want to be neutral and don’t want to voter for a give request, we can return ACCESS_ABSTAIN as a response. The second important method is the supports() method.

@Override
public boolean supports(ConfigAttribute attribute) {
  return true;
}

This method returns whether the voter support a particular configuration. We can use this in case we want the voter to only voter in case it meets specific configurations. In our case, we don’t have any such configuration and returning true which shows that our voter will support all kinds of authentication decision. The other variation of this method returns if the voter can vote for the secured object or not.

/**
 * All classes are supported.
 *
 * @param clazz the class.
 * @return true
*/
public boolean supports(Class << ? > clazz) {
    return true;
}

4. The AccessDecisionManager

The AccessDecisionManager is responsible to makes a final access control (authorization) decision. By default Spring security uses the AbstractAccessDecisionManager which contains a list of AccessDecisionVoters. These voters are responsible to voter and provide final authorization decision. Here are the three implementations provided by Spring Security.

Custom Access Decision Voter
  • ConsensusBased – Grants access if there are more ACCESS_GRANTED votes than the ACCESS_DENIED. It won’t count ACCESS_ABSTAIN.
  • AffirmativeBased – Grants access if any of the voter returns ACCESS_GRANTED.
  • UnanimousBased – Grant access if every voter either returns ACCESS_GRANTED or ACCESS_ABSTAIN.

5. Configuration Custom Access Decision Voter

The last part of the Custom Access Decision Voter in Spring Security is the configuration of the voter in our application. We will use our custom security configuration class, which extends the WebSecurityConfigurerAdapter. Here is how the code looks like:

@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {


    @Resource
    private CustomAccessDecisionVoter customAccessDecisionVoter;

    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List < AccessDecisionVoter << ? extends Object >> decisionVoters = Arrays.asList(
            new AuthenticatedVoter(),
            new RoleVoter(),
            new WebExpressionVoter(),
            customAccessDecisionVoter
        );
        return new UnanimousBased(decisionVoters);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login", "/register", "/home", "/external-login")
            .permitAll()
            .antMatchers("/account/**").hasAuthority("CUSTOMER")
            .accessDecisionManager(accessDecisionManager())

            ...
    }
}

Summary

In this article, we talked about the Custom Access Decision Voter. We saw how to customize the authorization workflow by injecting our own custom decision voter. We also learned about the decision voter provided by Spring security. In the last step we saw how to use the AscessDecsionManager to configure our custom decision voter.