Solucionado (ver solução)
Solucionado
(ver solução)
5
respostas

Não consigo injetar o UsuarioDAO em SecurityConfiguration

Recebo a seguinte stack-trace quando tento inicializar o servidor:

GRAVE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private br.com.casadocodigo.loja.daos.UsuarioDAO br.com.casadocodigo.loja.configuration.SecurityConfiguration.usuarioDAO; nested exception is java.lang.IllegalArgumentException: Can not set br.com.casadocodigo.loja.daos.UsuarioDAO field br.com.casadocodigo.loja.configuration.SecurityConfiguration.usuarioDAO to com.sun.proxy.$Proxy47
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:326)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1204)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:725)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4743)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private br.com.casadocodigo.loja.daos.UsuarioDAO br.com.casadocodigo.loja.configuration.SecurityConfiguration.usuarioDAO; nested exception is java.lang.IllegalArgumentException: Can not set br.com.casadocodigo.loja.daos.UsuarioDAO field br.com.casadocodigo.loja.configuration.SecurityConfiguration.usuarioDAO to com.sun.proxy.$Proxy47
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:542)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:323)
    ... 22 more
Caused by: java.lang.IllegalArgumentException: Can not set br.com.casadocodigo.loja.daos.UsuarioDAO field br.com.casadocodigo.loja.configuration.SecurityConfiguration.usuarioDAO to com.sun.proxy.$Proxy47
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
    at java.lang.reflect.Field.set(Unknown Source)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:538)
    ... 24 more

UsuárioDAO

package br.com.casadocodigo.loja.daos;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

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

import br.com.casadocodigo.loja.models.Usuario;

@Repository
@Transactional
public class UsuarioDAO implements UserDetailsService {

    @PersistenceContext
    private EntityManager manager;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<Usuario> result = this.manager.createQuery("select u from Usuario u where u.email = :email", Usuario.class)
                .setParameter("email", username).getResultList();

        if (result.isEmpty()) {
            throw new IllegalArgumentException("Usuário não foi encontrado");
        }

        return result.get(0);
    }
}

Usuário

package br.com.casadocodigo.loja.models;

import java.util.Collection;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import org.hibernate.validator.constraints.NotBlank;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
public class Usuario implements UserDetails {

    private static final long serialVersionUID = 1L;

    @Id
    private String email;

    @NotBlank
    private String senha;

    private String nome;

    @OneToMany(fetch=FetchType.EAGER)
    private List<Role> permissoes;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.permissoes;
    }

    @Override
    public String getPassword() {
        return this.senha;
    }

    @Override
    public String getUsername() {
        return this.email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean isEnabled() {
        // TODO Auto-generated method stub
        return true;
    }

    public String getNome() {
        return nome;
    }

}

Role

package br.com.casadocodigo.loja.models;

import javax.persistence.Entity;
import javax.persistence.Id;

import org.springframework.security.core.GrantedAuthority;

@Entity
public class Role implements GrantedAuthority {

    private static final long serialVersionUID = 1L;

    @Id
    private String nome;

    @Override
    public String getAuthority() {
        return this.nome;
    }

}

AppWebConfigurator

package br.com.casadocodigo.loja.configuration;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import com.google.common.cache.CacheBuilder;

import br.com.casadocodigo.loja.controllers.HomeController;
import br.com.casadocodigo.loja.daos.ProdutoDAO;
import br.com.casadocodigo.loja.infra.FileSaver;
import br.com.casadocodigo.loja.models.Carrinho;

// ativa o módulo de MVC para o projeto
@EnableWebMvc
// diz para o Spring quais são os pacotes que ele deve escanear a partir das
// classes informadas.
@ComponentScan(basePackageClasses = { HomeController.class, ProdutoDAO.class, FileSaver.class, Carrinho.class })
@EnableCaching
public class AppWebConfiguration extends WebMvcConfigurerAdapter {

    /**
     * Diz para o Spring qual é o prefixo e sulfixo da localização dos arquivos
     * de JSPs dentro da pasta WEB-INF
     * 
     * @return @Bean (classe gerenciada) do Spring
     */
    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");

        resolver.setExposedContextBeanNames("carrinho");

        return resolver;
    }

    /**
     * Método responsável por configurar a externalização do arquivo padrão de
     * mensagens da aplicação, configurando seu Encoding e também o tempo para
     * recarrega-lo, importante para que não seja necessário o restart da
     * aplicação quando o arquivo de mensagens for modificado.
     * 
     * @return {@link ReloadableResourceBundleMessageSource}
     */
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource message = new ReloadableResourceBundleMessageSource();
        message.setBasename("/WEB-INF/messages");
        message.setDefaultEncoding("UTF-8");
        message.setCacheSeconds(1);

        return message;
    }

    /**
     * Método responsável por manipular os padrões de datas no sistema,
     * incluindo seus registradores.
     * 
     */
    @Bean
    public FormattingConversionService mvcConversionService() {
        DefaultFormattingConversionService service = new DefaultFormattingConversionService();
        DateFormatterRegistrar register = new DateFormatterRegistrar();
        register.setFormatter(new DateFormatter("dd/MM/yyyy"));
        register.registerFormatters(service);
        return service;
    }

    /**
     * Método responsável por configurar o Multipart de arquivos enviados em
     * formulários, para indexação e disponibilização no servidor
     * 
     * @return
     */
    @Bean
    public MultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }

    /**
     * Método que ativa o gerenciamento dos arquivos (na verdade não sei bem o
     * porquê dele estar aqui)
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /**
     * Método responsável por configurar o acesso externo de servidores REST,
     * acessando-os a partir da implementação provinda pelo Spring
     * 
     * @return
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    /**
     * Método responsável por configurar o Guava, gerenciador de caches da
     * aplicação feita pelo Google
     * 
     * @return
     */
    @Bean
    public CacheManager cacheManager() {
        CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES)
                .initialCapacity(100);
        GuavaCacheManager manager = new GuavaCacheManager();
        manager.setCacheBuilder(builder);
        return manager;
    }

    @Bean
    public ViewResolver contentNegotiation(ContentNegotiationManager manager) {
        List<ViewResolver> viewResolvers = new ArrayList<>();
        viewResolvers.add(internalResourceViewResolver());
        viewResolvers.add(new JsonViewResolver());

        ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
        resolver.setViewResolvers(viewResolvers);
        resolver.setContentNegotiationManager(manager);

        return resolver;
    }
}

Alguma ideia?

5 respostas

Fala aí Leonardo tudo bem?

Consegue enviar o código da classe SecurityConfiguration?

Eu procurei e vi que o problema está relacionado às dependências do projeto....

Tentei as soluções já encontradas aqui no fórum:

https://cursos.alura.com.br/forum/topico-error-creating-bean-with-name-springsecurityfilterchain-29136

https://cursos.alura.com.br/forum/topico-erro-ao-subir-a-aplicacao-com-spring-security-28736

https://cursos.alura.com.br/forum/topico-duvida-no-ex-2-da-aula-4-personalizando-o-login-e-logout-22707

Mas nenhuma delas resolveu o meu problema...

Baixei o pom.xml do projeto disponível no github.

Consigo sim:

SecurityConfiguration

package br.com.casadocodigo.loja.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import br.com.casadocodigo.loja.daos.UsuarioDAO;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UsuarioDAO usuarioDAO;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/produtos/form").hasRole("ADMIN")
            .antMatchers("/carrinho").permitAll()
            .antMatchers("/produtos").hasRole("ADMIN")
            .antMatchers("/produtos/**").permitAll()
            .antMatchers("/").permitAll()
            .anyRequest().authenticated()
            .and().formLogin();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(usuarioDAO).passwordEncoder(new BCryptPasswordEncoder());
    }

}

SpringSecurityFilterConfiguration

package br.com.casadocodigo.loja.configuration;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SpringSecurityFilterConfiguration extends AbstractSecurityWebApplicationInitializer{

}
solução!

Consegui resolver meu problema.

Com algumas pesquisas, vi que o Spring não consegue criar um proxy do meu UsuarioDAO caso ele esteja implementando UserDetailsService e ainda ser uma classe com @Transactional (sim, testei sem a anotation e ele funcionou, mas não era isso que eu queria)

Então eu criei uma nova interface:

UsuarioDAO

package br.com.casadocodigo.loja.daos;

import org.springframework.security.core.userdetails.UserDetailsService;

public interface UsuarioDAO extends UserDetailsService{

}

e o meu UsuarioDAO passou a se chamar UsuarioDAOImpl, porque a partir de agora ele não tem ligação direta com a classe, fazendo com que o Spring consiga criar um proxy para ele. A classe ficou:

package br.com.casadocodigo.loja.daos;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;

import br.com.casadocodigo.loja.models.Usuario;

@Repository
public class UsuarioDAOImpl implements UsuarioDAO {

    @PersistenceContext
    private EntityManager manager;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<Usuario> result = this.manager.createQuery("select u from Usuario u where u.email = :email", Usuario.class)
                .setParameter("email", username).getResultList();

        if (result.isEmpty()) {
            throw new IllegalArgumentException("Usuário não foi encontrado");
        }

        return result.get(0);
    }
}

e o meu SecurityConfiguration passou a receber um UsuarioDAO

package br.com.casadocodigo.loja.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import br.com.casadocodigo.loja.daos.UsuarioDAO;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UsuarioDAO usuarioDAO;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/produtos/form").hasRole("ADMIN")
            .antMatchers("/carrinho").permitAll()
            .antMatchers("/produtos").hasRole("ADMIN")
            .antMatchers("/produtos/**").permitAll()
            .antMatchers("/").permitAll()
            .anyRequest().authenticated()
            .and().formLogin();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(usuarioDAO).passwordEncoder(new BCryptPasswordEncoder());
    }

}

Não sei se é a melhor solução, mas, para meu caso resolveu.

Alguma melhoria?

Eu mesmo reparei que poderia haver uma melhoria em: criei uma interface herdando de UserDetailsService que, nesse caso, seria desnecessária.

A composição e a injeção feita pelo Spring podem ser feitas a partir de uma instancia de UserDetaisService, sem nenhum problema.