

Table of Contents
1. Overview
In this article, we will explain about Spring security custom rolevoter example. Spring security provides role-based voting based on ULR or resources but sometimes we should require role voter more specific. We will also explain how we can implement spring security custom decision manager.
We are taking here an example to explain in more details, We have two roles in application admin
and staff
but staff cannot login in the application during the weekend (Sunday). If staff
try to login during the weekend then an application will return 405
access denied status.
- we have created
accessDecisionManager
which contains a list of voters. - We have created a class
WeekOffVoter
which implementsAccessDecisionVoter
interface,AccessDecisionVoter
have avote
method in which we should write our custom code to take a decision where allowed to access decision or not.- vote method may return three possible value:
- ACCESS_DENIED: Deny to access resources
- ACCESS_GRANTED : Grant to access resources
- ACCESS_ABSTAIN : Not allowed nor Deny access resources, Decision will be take based on other voters
- vote method may return three possible value:
2. Example

spring security custom rolevoter
2.1 pom.xml
spring-boot-starter-security
requires for spring security other dependency is for spring boot.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>spring-boot-example</groupId> <artifactId>spring-boot-example</artifactId> <version>1.0-SNAPSHOT</version> <description>Spring security custom rolevoter example</description> <!-- Inherit defaults from Spring Boot --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> </parent> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/com.lambdaworks/lettuce --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <!-- for tomcat web container--> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <!--starter require for spring boot spring security--> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <!--fot jap compilation need provide scope runtime or provided because it available in tomcat --> <artifactId>tomcat-embed-jasper</artifactId> <scope>runtime</scope> <!-- in my case provided not working so write runtime--> </dependency> </dependencies> <!-- Package as an executable jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.2 application.properties
spring.mvc.view.prefix: /WEB-INF/jsp/ spring.mvc.view.suffix: .jsp
2.3 SecurityConfiguration
- Here we have created
AccessDecisionManager
Bean and this bean has been registered withaccessDecisionManager
:
package com.javadeveloperzone; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.access.vote.UnanimousBased; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.web.access.expression.WebExpressionVoter; import java.time.LocalDateTime; import java.util.Arrays; import java.util.Collection; /** * Created by JavaDeveloperZone on 13-11-2017. */ @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired // here is configuration related to spring boot basic authentication public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password("mypassword").roles("STAFF") .and() .withUser("admin").password("mypassword").roles("ADMIN");// those are user name and password } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/loginFailed").permitAll() .and().authorizeRequests().antMatchers("/**").access("hasAnyRole('ADMIN','STAFF')") .and().authorizeRequests() .and() .formLogin() .defaultSuccessUrl("/loginSuccess") .failureUrl("/failed") .loginPage("/login") .permitAll(); http.authorizeRequests() .accessDecisionManager(accessDecisionManager()); // passed custom access decision manager } @Bean public AccessDecisionManager accessDecisionManager() { java.util.List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList( new WebExpressionVoter(), new RoleVoter(), new AuthenticatedVoter(), new WeekOffVoter() // create instance of WeekOffVoter ); return new UnanimousBased(decisionVoters); } }
2.4 WeekOffVoter
We have defined weekOffVoter
class which implements an interface AccessDecisionVoter.
vote
method contains custom logic to deny login access during the week off for staff members:
package com.javadeveloperzone; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import java.time.LocalDateTime; import java.util.Collection; /** * Created by JavaDeveloperZone on 22-01-2018. */ class WeekOffVoter implements AccessDecisionVoter { @Override public int vote(Authentication authentication, Object object, Collection collection) { boolean isRoleUser = authentication.getAuthorities().stream() .filter(e -> e.getAuthority().equals("ROLE_STAFF")) .findAny().isPresent(); // check is staff role if (isRoleUser) { if (LocalDateTime.now().getDayOfWeek().getValue() == 7) // check for sunday return ACCESS_DENIED; // deny if sunday else { return ACCESS_ABSTAIN; // not granted or not deny, Decision based on other voter } } else { return ACCESS_ABSTAIN; // not granted or not deny, Decision based on other voter } } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class clazz) { return true; } }
2.5 Application
package com.javadeveloperzone; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; /** * Created by JavaDeveloperZone on 19-07-2017. */ @SpringBootApplication @ComponentScan // Using a root package also allows the @ComponentScan annotation to be used without needing to specify a basePackage attribute public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); // it wil start application } }
2.6 LoginController
package com.javadeveloperzone.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.security.Principal; /** * Created by JavaDeveloperZone on 19-07-2017. */ @Controller public class LoginController { @RequestMapping(value = "/login") public String login() { return "login"; } @RequestMapping(value = "/loginSuccess") public String loginSuccess(Principal principal, ModelMap modelMap) { modelMap.put("userName",principal.getName()); return "welcome"; } @RequestMapping(value = "/loginFailed") public String loginFailed() { return "loginFailed"; } }
2.7 login.jsp
<%-- Created by IntelliJ IDEA. User: Java Developer Zone Date: 18-03-2017 Time: 07:34 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Spring security custom rolevoter example</title> </head> <body> <H2>STAFF Can't Login on SUNDAY</H2> <form name='f' action='/login' method='POST'> User: <input type='text' name='username' value=''> Password: <input type='password' name='password'/> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <input name="submit" type="submit" value="Login"/> </form> </body> </html>
2.8 welcome.jsp
<%-- Created by IntelliJ IDEA. User: Java Developer Zone Date: 18-03-2017 Time: 07:34 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Spring security custom rolevoter example</title> </head> <body> Welcome, ${userName} </br> <a href="/logout">Logout</a> </body> </html>
2.9 loginFailed.jsp
<%-- Created by IntelliJ IDEA. User: Java Developer Zone Date: 18-03-2017 Time: 07:34 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Spring security custom rolevoter example</title> </head> <body> Login Failed. </body> </html>
Output:
Let’s try staff login on Sunday:

Spring security custom rolevoter example – Login
It will throw 405 - Access is denied
because of custom role voter deny it.

Spring security custom rolevoter example – Access Deny
3. Conclusion
In this example, We learned about how we can implement role-based custom authorization to allowed or deny access the resources. We can write our custom code using custom voter implementation in spring security.
2 comments. Leave new
Is it possible to implement in spring oAuth 2?
Hi Akhil,
It part HttpSecurity Security. You can implement for OAuth2.0:
here is authentication server: https://javadeveloperzone.com/spring-boot/spring-security-oauth-2-0-authentication-server/
resource server: https://javadeveloperzone.com/spring-boot/spring-security-oauth-2-0-resource-server-example/