How to create Permissions (Permissions) instead of Roles (Roles) in Spring Security?

Asked

Viewed 4,712 times

18

I am developing an Access Control module for my web application using Spring Security (3.1.4 - the latest release) and ran into a limitation in the mechanism of Authorization, as the framework only provides for the use of Roles (roles), which means I can only specify which user has or has not a certain role.

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

Suppose my system I have users the profiles Aluno, Professor and Diretor. Given an administrative screen X, Aluno you have the permission denied (false), Professor you do not have the permission undefined (null) and Diretor you have the permission granted (true).

But the system allows to give specific permissions to users in addition to profiles. If I grant permission to screen X to a user in the profile Aluno, 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 he could have access. If grant to Diretor it will simply continue to have access as it does not affect the permission set in the profile.


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

Obviously I’ve read all the documentation of Spring Security, although the best references are from other blogs, but I haven’t been able to extract from all this a good strategy to solve the issue.

Is there a mechanism in Spring Security, which 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 method hasRole() is called?

  • 3

    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/

  • 2

    +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.

  • 1

    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.

  • 1

    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

2 answers

12


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:

inserir a descrição da imagem aqui

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>
  • Since I didn’t get any real answers, I had to mark my own as a sure thing. But I thank everyone who commented and shared relevant information.

  • I need to implement a security through spring, only I can not work with rules. but screen level permissions (xhtml). I saw this your post, and I didn’t completely understand your post. but summarizing.. this your implementation would solve in parts my problem?

  • 1

    @rodrigoCristoFuhr It makes no difference if you apply security to a method, URL or button on a screen. All you need to do is assign the user a set of permissions and associate each permission to a system element. On the screens you can check if the user has an X permission through Taglibs. View documentation. If you have any more questions, ask! Hug!

  • You could post the Customuserdetails class and the Managed class

  • 2

    This is very interesting. It’s just how Symfony Framework does (by the way, Symfony’s security components are based on Spring) through Voters. They can replace ACL in multiple scenarios.

4

All hail, @utluiz!

I don’t have enough reputation to make a comment, but I’d like to leave my contribution.

You took a look at Spring Security ACL?

Complex Applications often will find the need to define access Permissions not imply 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 current Domain Object instance Subject of a method Invocation.

I believe it can help your implementation as it works directly in the domain’s relationship with the user. This way you can restrict access directly to the resource. There is even an interesting taglib for checking users' access.

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

Example taken from from here

It is a Spring Security module and requires the creation of a table structure in your database. If you don’t have that kind of restraint, I believe it’s worth taking a look.

Up until!

  • Hello! This module is very promising. Unfortunately, the project in which I used such a security scheme was already some 2 years ago and the tables were defined by the client. They came up with a scheme so confusing that no framework would (want to) support it. Maybe they could have made a mix of approaches. But thanks anyway. D

  • 2

    Oops! It was a pleasure. If you have anything else to put on it, give it a touch. Security is always a delicate part of any project and people have a tendency to neglect or think it’s easy, but in fact the hole is always lower.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.