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 givenDecisionVoter
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 Name | Description |
---|---|
WebExpressionVoter | Voter which handles web authorization decisions.This enable us to use SpEL. e.g .antMatchers("/account/**").hasAuthority("CUSTOMER") |
AuthenticationVoter | Cast 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. |
RoleVoter | Votes 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.
ConsensusBased
– Grants access if there are moreACCESS_GRANTED
votes than theACCESS_DENIED
. It won’t countACCESS_ABSTAIN
.AffirmativeBased
– Grants access if any of the voter returnsACCESS_GRANTED
.UnanimousBased
– Grant access if every voter either returnsACCESS_GRANTED
orACCESS_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.