The Spring Framework decision system
After a deeper analysis of the structure of Spring I solved the issue with a Spring Bean that implements the interface AccessDecisionVoter
. A Voter (Voter) is a class that Spring invokes to say if determined credential can access determined recourse in a given context.
Spring allows you to chain (chain) miscellaneous Voters to make a certain decision. Each Voter vote in favour, against or abstain from the decision. The final decision is taken by AccessDecisionManager
, which has the following implementations:
AffirmativeBased
: allows access if at least one Voter voted in favour.
UnanimousBased
: allows access only if everyone voted in favour, ignoring abstentions.
ConsensusBased
: allows or denies access according to the majority.
That way, I can implement different Voters that respond to different situations and abstain from other situations. In addition, I choose the decision strategy that best fits my system.
The above statements can be better understood with the following diagram:
Remark: I was given the suggestion to extend AccessDecisionManager
, which would solve the problem, but at the same time would overwrite practically completely Spring Security. So it seemed better to act on a more specific point.
Implementation
Create a Voter it is not a complex task, but it took several tests to understand well the functioning, the values received in the parameters and the overall behavior of the framework, as there is no detailed documentation on this. Below is a simplified implementation of what I did, with no code specific to my domain:
public class CustomVoter implements AccessDecisionVoter<Object> {
final protected Logger logger = LoggerFactory.getLogger(getClass());
@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) {
logger.info("### Controle de Acesso ###");
//verifica se as credenciais são do tipo esperado
if (authentication.getPrincipal() instanceof CustomUserDetails) {
CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
GerenciadorPermissao gerenciadorPermissao = user.getGerenciadorPermissao();
//variável de sessão (HTTP) armazenada numa determinada tela via JSF
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
Integer variavelSessao = (Integer) session.getAttribute("variavelSessao");
Boolean result = null;
for (ConfigAttribute configAttribute : attributes) {
String attr = configAttribute.getAttribute();
if (attr.equals("ROLE_USER")) {
//ROLE_USER é a ROLE de usuário, logado, então retorna true sempre
result = true;
} else {
//chama uma lógica específica que verifica se o usuário possui permissão no contexto atual
result = gerenciadorPermissao.verificarPermissao(variavelSessao, attr);
}
}
if (result == null || result == Boolean.FALSE) {
logger.info(" -> Acesso Negado!");
return ACCESS_DENIED;
} else {
logger.info(" -> Acesso Permitido!");
return ACCESS_GRANTED;
}
} else {
System.out.println(" -> Não é do tipo CustomUserDetails!");
return ACCESS_ABSTAIN;
}
}
}
Some relevant points of the above code:
- The method
vote()
is called by Spring whenever he needs to make a decision.
- The first parameter, of type
Authentication
, returns information about the current login.
- The method
getPrincipal
of the instance of Authentication
returns the user information I defined at the time of login. In this case it is an instance of CustomUserDetails
, implementing the interface UserDetails
spring.
- As can be seen, it is nothing complicated to access session values (
HttpSession
) on web systems with JSF.
- The parameter of the type
Object
returns the resource being accessed. Depending on the context, it can be the URL of a page, it can be a reference to a method, etc.
- The last parameter contains a list of attributes, rules or roles that should be checked.
Setup
The following XML document is a simplification of the Spring Security configuration used in my application. I published it in its entirety, because in some examples I found on other sites it was not clear as a bean related with the others.
In short, the setting contains:
- General definitions of Spring Security.
- Statement of Voter and its association with a
AccessDecisionManager
of the kind UnanimousBased
.
- Security of some pages associated with
AccessDecisionManager
avowed.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- Configura o servlet filter do Spring Security -->
<beans:bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<beans:constructor-arg>
<beans:list>
<filter-chain filters="securityContextPersistenceFilter" pattern="/**" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<beans:property name="forceEagerSessionCreation" value="true" />
</beans:bean>
<!-- Configura handlers para sucesso de autenticação, falha de autenticação e acesso negado a um determinado recurso -->
<beans:bean
id="customAuthenticationSuccessHandler"
class="br.com.starcode.commons.security.CustomAuthenticationSuccessHandler" />
<beans:bean
id="customAuthenticationFailureHandler"
class="br.com.starcode.commons.security.CustomAuthenticationFailureHandler" />
<beans:bean
id="customAccessDeniedHandler"
class="br.com.starcode.commons.security.CustomAccessDeniedHandler" />
<!-- Voter customizado -->
<beans:bean id="customVoter" class="br.com.starcode.commons.security.CustomVoter" />
<!-- Define AccessDesisionManager como UnanimousBased e coloca o Voter na lista -->
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
<beans:ref bean="customVoter" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
<!-- Configura a criptografia (hash) da senha -->
<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg>
<beans:value>11</beans:value>
</beans:constructor-arg>
</beans:bean>
<!-- Configura o AuthenticationManager com os beans da aplicação -->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="accessControlService">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<!-- Não coloca segurança em Javascript, CSS e outros recursos "estáticos" do JSF -->
<http pattern="/javax.faces.resource/**" security="none" />
<!-- Não coloca segurança na tela de login -->
<http pattern="/login.xhtml*" security="none" />
<!-- Define a segurança para os demais recursos -->
<http auto-config="true" use-expressions="false" access-decision-manager-ref="accessDecisionManager">
<!-- Referência ao controlador de Acesso Negado -->
<access-denied-handler ref="customAccessDeniedHandler" />
<!-- Informações da página de login e dos handlers de sucesso e falha -->
<form-login
login-page="/login.xhtml"
authentication-success-handler-ref="customAuthenticationSuccessHandler"
authentication-failure-handler-ref="customAuthenticationFailureHandler" />
<!-- Página de logout, desloga o usuário ao ser chamada -->
<logout logout-url="/logout.xhtml" />
<!-- Acesso à página inicial para qualquer usuário logado -->
<intercept-url access="ROLE_USER" pattern="/index.xhtml*" />
<!-- permissões específicas para as telas -->
<intercept-url access="permissao.tela.1" pattern="/tela1.xhtml" />
<intercept-url access="permissao.tela.2" pattern="/tela2.xhtml" />
<intercept-url access="permissao.tela.N" pattern="/telaN.xhtml" />
<!-- nega acesso a qualquer outra tela -->
<intercept-url access="NO_ACCESS" pattern="/**.xhtml" />
</http>
<!-- Permite anotar métodos -->
<global-method-security
secured-annotations="enabled"
jsr250-annotations="enabled"
pre-post-annotations="enabled" />
</beans:beans>
I don’t know if that’s what you’re thinking, but here on the job people chose to use spring authentication and RBAC authorization. There is an article in English that helped us a lot(although we are in br.stack I will suggest this article because it helped us define it) http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-activity-based-checks/
– leomeurer
+1 in what @Meurer said. Unfortunately, many frameworks encourage role-based authorization models that are difficult to apply to troubleshooting the type of problems you’re facing, which are common requirements. One idea is to use Spring Security for basic authentication/authorization and implement another RBAC-based authorization method.
– elias
Thanks @Meurer, it’s a great article, although it’s not 100% applicable in my current situation, because the conceptual definition is already closed and my problem is more technical with regard to the limitations of the framework. and Lias, it’s true, in my current code I’m already practically redoing all of Spring’s authorization logic, but it seems to me to be too intrusive, because changes in the framework will probably break my implementations. Anyway Thanks for the comments.
– utluiz
Maybe what you want is to create your own Design manager, extending Abstractaccessdecisionmanager. See http://docs.spring.io/autorepo/docs/spring-security/3.0.x/reference/authz-arch.html#authz-access-Voting
– dstori