How to create real Permissions instead of simple Roles in Spring Security?

17

I'm developing an Access Control module for my web application using Spring Security (3.1.4 - the latest release ) and ran into a limitation on the Authorization , because the framework only provides for the use of Roles (roles), which means that I can only specify that the user has or does not have one certain paper.

However, Access Control requirements require the use of permissions, even with hierarchy.

Suppose my system has users with the Aluno , Professor and Diretor profiles. Given an administrative screen X, Aluno has the permission denied (false), Professor does not have undefined permission and Diretor has permission < in> granted (true).

But the system allows you to give users specific permissions in addition to profiles. If I grant permission to the X screen to a user in the Aluno profile, he will not have the permission because the profile has already denied and has priority. If granted to Professor it will have access to screen X, because the profile did not define whether or not it could have access. If granted to Diretor it will simply continue to have access as it does not affect the permission set in the profile.

Translating all this in more technical terms, I need to extend the Authorization mechanism of Spring Security with a particular algorithm.

Obviously I've read all the Spring Security documentation, although the best references are from other blogs, but I have not yet been able to extract a good strategy to resolve the issue.

Is there a mechanism in Spring Security that I can override, where I can implement a method where I have access to the context (HttpSession, for example) and can return true or false whenever the user tries to access a protected resource or is the hasRole() method called?

    
asked by anonymous 12.12.2013 / 13:20

2 answers

11

The Spring Framework Decision System

After a more thorough analysis of the Spring framework I solved the issue with a Spring Bean that implements the AccessDecisionVoter . A Voter is a class that Spring invokes to tell if a given credential can access a given resource / em>.

Spring allows you to chain several Voters to make a decision. Each Voter votes in favor, against or abstains from the decision. The final decision is AccessDecisionManager , which has the following implementations:

  • AffirmativeBased : allows access if at least one Voter voted in favor.
  • UnanimousBased : allows access only if everyone voted in favor, ignoring abstention.
  • ConsensusBased : allows or denies access according to the majority.

In this way, I can implement different Voters that respond to different situations and abstain from others. In addition, I choose the decision strategy that best fits my system.

The above statements can be best understood with the following diagram:

Note:IwasgiventhesuggestiontoextendAccessDecisionManager,whichwouldsolvetheproblem,butatthesametimewouldoverwritepracticallycompletelySpringSecurity.Soitseemedtomebesttoworkonamorespecificpoint.

Implementation

Voterisnotacomplextask,butittookseveralteststounderstandtheoperation,thevaluesreceivedintheparameters,andthegeneralbehavioroftheframework,asthereisnotadetaileddocumentationonthat.BelowisasimplifiedimplementationofwhatIdid,withoutmydomain-specificcode:

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 code points above:

  • The vote() method is called by Spring whenever it needs to make a decision.
  • The first parameter, of type Authentication , returns information about the current login.
  • The getPrincipal method of the Authentication instance returns the user information that I set at login time. In this case it is an instance of CustomUserDetails , which implements the interface UserDetails of Spring.
  • As can be seen, it is not at all complicated to access session values ( HttpSession ) on web systems with JSF.
  • The parameter of type Object returns the resource being accessed. Depending on the context, it could be the URL of a page, it could be a reference to a method, etc.
  • The last parameter contains a list of the attributes, rules, or roles that should be checked.

Configuration

The following XML document is a simplification of the Spring Security configuration used in my application. I published it in full, because in some examples I found on other sites it was not clear how a bean related to the others.

In short, the configuration contains:

  • General Spring Security settings.
  • Voter declaration and its association with a AccessDecisionManager of type UnanimousBased .
  • Security of some pages associated with AccessDecisionManager declared.
<?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>
    
16.01.2014 / 12:43
3

Save, @utlu!

I do not have enough reputation to make a comment, but I would like to leave my contribution.

You took a look at the Spring Security ACL ?

  

Complex applications often will find the need to define access permissions not simply at a web request or method invocation level. Instead, security decisions need to comprise both who ( Authentication ), where ( MethodInvocation ) and what ( SomeDomainObject ). In other words, authorization decisions also need to consider the actual domain object instance of a method invocation.

I believe that it can help in its implementation since it works directly in the relationship of the domain with the user. This way you can restrict access directly to the resource. There is even an interesting taglib for user access checking.

@PreAuthorize("hasPermission(#entityId, 'EntityClass', 'read')")
@RequestMapping("/entities/{entityId}") 
public String fetchEntity(@PathVariable("entityId") String entityId) {...}

Sample removed from here

It is a Spring Security module and requires the creation of a table structure in your database. If you do not have this kind of restriction, I think it's worth taking a look.

Until!

    
14.07.2015 / 20:11