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