Customizando o SpringSecurity

Salve rapaziada, este post era pra ter saido a um bom tempo junto com uma série de posts sobre JSF entretanto a falta de tempo acabou impedindo que eu concluísse esses posts.

Provavelmente você deve estar pesquisando algo sobre o Spring Security 4 mas os conceitos centrais não mudaram da Spring Security 3.1.3.RELEASE para a versão 4.

Quando trabalhamos com o Spring Security geralmente fazemos a autenticação de usuários na aplicação através do acesso à base de dados ou através de web-serviçes ou de uma forma mais complicada através de uma mistura dos dois. Então fica a pergunta e como eu faço para conseguir autenticar os usuários em cenários diferentes destes.
Podemos fazer como no agradável exemplo abaixo em que autenticamos os usuários em memória.

<authentication-manager>
    <authentication-provider>
      <user-service>
        <user name="eudes" password="senhadoeudes" authorities="ROLE_USER, ROLE_ADMIN">
        <user name="gabriel" password="senhadogabriel" authorities="ROLE_USER">
		<user name="leandro" password="senhadoleandro" authorities="ROLE_USER">
		<user name="tiago" password="senhadotiago" authorities="ROLE_USER">
      </user></user></user></user></user-service>
    </authentication-provider>
  </authentication-manager>

Entretanto é altamente improvável que o seu sistema só tenha alguns usuários cadastrados em modo hardcoded. O Spring Security nos provê um kit de autenticação completo voltado para o banco de dados, mas é preciso que a criação e existência de tabelas estejam exatamente da forma requerida, o que nem sempre atende, assim, se quisermos autenticar, nossos usuários, através de web-services, estaremos, mais uma vez, presos ao kit padrão do Spring Security.
A resposta para este tipo de problema está na personalização da AuthenticationProvider ou da UserDetailService. Se observarmos a UserDetails apenas verifica se um usuário existe ou não através de seu nome de usuário. Diante disso fica a pergunta apenas nome de usuário e senha são suficientes para garantir a segurança do sistema?

public UserDetails loadUserByUsername(String username)
	throws UsernameNotFoundException {
}

O que é mais frustrante é que você dificilmente encontra algum exemplo decente, explicando como extender a AuthenticationProvider, e a maioria dos exemplos lhe ensinam como implementar UserDetailService, entretanto de forma muito breve e simplificada.
Aqui estão as instruções faltantes até mesmo da documentação Spring Security pois você certamente bateu bem a cabeça até chegar a uma solução que funcione seja ela através AuthenticationProvider ou da UserDetailService.
O que você definitivamente precisa é personalizar AuthenticationProvider, que é a classe base para realizar autenticação. Então fica a pergunta por que a Spring Source não nos informa direito como implementar AuthenticationProvider ou a UserDetailService?
Conferindo a classe AbstractUserDetailsAuthenticationProvider, que extende a AuthenticationProvider, podemos verificar um método interessante:

protected abstract UserDetails retrieveUser(java.lang.String username,
	UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException

Não é exatamente isso o que a UserDetails e a UserDetailService nos fornece? Uma outra opção que temos é estender AbstractUserDetailsAuthenticationProvider em vez da AuthenticationProvider, mas ainda assim UserDetailService é muito mais popular.
Você pode simplesmente implementar UserDetailService para conseguir personalizar o seu processo de autenticação a partir de um banco de dados ou de um web-service bastando fornecer apenas um nome de usuário e senha para compará-los com as informações do usuário para conseguir a autenticação.
O que acontece no fim das contas é que apenas fornecer um nome de usuário para o UserDetailService, é o suficiente para retornar uma UserDetails que tem a senha do usuário, então DaoAuthenticationProvider será responsável por acessar o banco de dados e verificar as credenciais inseridas pelo usuário no formulário de login através da UserDetails.
Enfim encontrar o ponto certo de personalização é importante, e um sinal de elegância no desenvolvimento de software.
O DaoAuthenticationProvider é o AuthenticationProvider padrão se no arquivo de configuração de autenticação “spring-security.xml” você não mencionar qualquer AuthenticationProvider, então o Spring Security utiliza por padrão o DaoAuthenticationProvider. Então se você fizer assim:

<authentication-manager>
    <authentication-provider>
      <user-service>
        <user name="eudes" password="senhadoeudes" authorities="ROLE_USER, ROLE_ADMIN">
        <user name="gabriel" password="senhadogabriel" authorities="ROLE_USER">
		<user name="leandro" password="senhadoleandro" authorities="ROLE_USER">
		<user name="tiago" password="senhadotiago" authorities="ROLE_USER">
      </user></user></user></user></user-service>
    </authentication-provider>
</authentication-manager>

Ele estará usando o DaoAuthenticationProvider.

<authentication-manager>
	<authentication-provider user-service-ref="semeruUserService">
        </authentication-provider>
    </authentication-manager>
<beans:bean id="semeruUserService" class="com.cleancode.springmvc1.users.UserServiceImpl">

Também no segundo caso, como é padrão, ele estará usando DaoAuthenticationProvider, mesmo que não esteja no seu arquivo de configuração.

Agora vou lhe mostrar dois exemplos de como pode ser feito

Exemplo 1 Customização da AuthenticationProvider:

No primeiro exemplo apenas comparo se a senha e o usuário digitados são iguais se sim então o acesso é liberado ao usuário. Obviamente em uma aplicação real você deve fazer algo mais sério que isso.

package br.com.semeru.security;

import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;

public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        System.out.println("O Login do cara é: " + authentication.getName());
        List<grantedauthority> auth = new ArrayList<grantedauthority>();
        //Use assim se você usa o Spring Security 3.0.5.RELEASE
        auth.add(new GrantedAuthorityImpl("ROLE_USER"));
        //Já na versão 3.1.3.RELEASE essa classe foi depreciada e você deve usar como no trecho abaixo
        //auth.add(new SimpleGrantedAuthority("ROLE_USER"));

		//Nesse exemplo eu apenas comparo se o nome de usuário e senha são iguais.
		//Caberia a você acessar os dados e fazer sua própria regra de autenticação.
        if (authentication.getName().equals(authentication.getCredentials())) {
            return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), auth);
        } else {
            return null;
        }

    }

    @Override
    public boolean supports(Class<!--? extends Object--> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

No seu arquivo de configuração (spring-security.xml) você define um bean chamado customAuthenticationProvider especificando o pacote e o nome da classe ao qual ele se refere.

<b:bean id="customAuthenticationProvider" class="br.com.semeru.security.CustomAuthenticationProvider"></b:bean>
    <authentication-manager>
        <authentication-provider ref="customAuthenticationProvider">
    </authentication-provider></authentication-manager>

Exemplo 2 Customização da UserDetailsService:

No exemplo abaixo estou usando nomes de usuário e senhas hardcoded como exemplo. Em uma aplicação real nesse ponto a única coisa que você precisaria fazer é acessar um banco de dados, serviço ou web-service para recuperar as informaçções do usuário à partir do nome de usuário, em seguida você cria uma instância de User e seta os valores de seu atributos de acordo com os dados do usuário, a senha só será usada mais tarde pelo DaoAuthenticationProvider

package br.com.semeru.security;

import br.com.semeru.model.dao.HibernateDAO;
import br.com.semeru.model.dao.InterfaceDAO;
import br.com.semeru.model.entities.Pessoa;
import br.com.semeru.util.FacesContextUtil;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service("semeruUserService")
public class UserServiceImpl implements UserDetailsService, Serializable {

    private static final long serialVersionUID = 1L;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<grantedauthority> auth = new ArrayList<grantedauthority>();

        if(username.equals("tiago"))
        {

            auth.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
            auth.add(new SimpleGrantedAuthority("ROLE_USER"));

			//Por padrão no nosso exemplo o usuário e a senha são iguais
            return new User("tiago",
                    "12fd5311017d4b8faf7abc6d7fa13d182f519a13",
                     auth);

        } else if (username.equals("gabriel")){

            auth.add(new SimpleGrantedAuthority("ROLE_USER"));
            return new User("gabriel",
                    "18a98c35f49808b45edadc75fb1b25ebfd4037d6",
                     auth);
        }
        else
        {
            throw new UsernameNotFoundException("Usuário não encontrado: " + username);
        }

    }

}

Por fim seu arquivo de configuração (spring-security.xml) deve se assemelhar ao trecho abaixo.

    <authentication-manager>
        <authentication-provider user-service-ref="semeruUserService">
            <password-encoder hash="sha">
        </password-encoder></authentication-provider>
    </authentication-manager>
    <b:bean id="semeruUserService" class="br.com.semeru.security.UserServiceImpl">

Como você pode ver que você não precisa declarar o DaoAuthenticationProvider como AuthenticationProvider. Acesse o projeto no GitHub para ter uma visão mais abrangente.

Divirta-se e bons códigos sem bugs.

Treinamentos relacionados com este post














Leandro Costa

Sou desenvolvedor de software a desde 2008, além de programar gosto de esportes de aventura como rapel, tirolesa, trilhas de bike, apreciador de cervejas, baladas, motos e do bom e velho Rock’n Roll também gosto de história, ficção científica e de tecnologia. Atualmente sou consultor de Agile Software Delivery na Erudio Training e instrutor na Udemy.

2 thoughts to “Customizando o SpringSecurity”

  1. olá…otimo post…parabéns…..

    Mas como eu faria pra informar mais parâmetros na tela de Login…..Como por exemplo, digamos que o sistema seja muito empresa e está cadastrado na Matriz e na Filial, mas no momento de Logar no sistema eu gostaria de informar a qual Filial eu estou cadastrado.

    Como isso eu quero manter essas informações na session durante todo o tempo que eu estiver acessando o sistema……

    desde já obrigado

    1. Você poderia criar uma tabela onde tem a lista de empresas e o funcionário é vinculado a uma delas assim ao buscar as informações do usuário também vai recuperar qual empresa ele se vincula.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *