无法自动装配字段:private org.springframework.security.core.userdetails.UserDetailsService

9

我刚开始使用Spring,所以一直在研究安全方面的事情。每次运行我的应用程序时,我都会遇到以下问题:

org.springframework.beans.factory.BeanCreationException:创建名为'securityConfig'的bean时注入自动装配依赖项失败;嵌套异常是org.springframework.beans.factory.BeanCreationException:无法自动装配字段:private org.springframework.security.core.userdetails.UserDetailsService com.entirety.app.config.SecurityConfig.userDetailsServiceImplementation;嵌套异常是org.springframework.beans.factory.NoSuchBeanDefinitionException:找不到符合条件的类型[org.springframework.security.core.userdetails.UserDetailsService]的bean来满足依赖关系:期望至少1个bean作为此依赖项的自动装配候选者。依赖注释:{@org.springframework.beans.factory.annotation.Autowired(required=true)}

我已经反复检查了我的代码,但无法确定问题出在哪里。

Spring Framework版本:3.2.5.RELEASE Spring Security版本:3.2.0.M2

SecurityConfig.java

package com.entirety.app.config;

import com.entirety.app.service.implement.UserDetailsServiceImplementation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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 javax.sql.DataSource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsServiceImplementation;

    @Override
    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.userDetailsService(userDetailsServiceImplementation)
                .authorizeUrls()
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .antMatchers("/sec/**").hasRole("MODERATOR")
                    .antMatchers("/*").permitAll()
                    .anyRequest().anonymous().and().exceptionHandling().accessDeniedPage("/denied").and()
                .formLogin()
                    .loginProcessingUrl("/j_spring_security_check")
                    .loginPage("/login")
                    .failureUrl("/error-login")
                .and()
                .logout()
                    .logoutUrl("/j_spring_security_logout")
                    .logoutSuccessUrl("/");
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

CrmUserService.java

@Service("userService")
@Transactional
public class CrmUserService implements UserDetailsService {
    @Autowired
    private UserDAO userDAO;

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {

        com.entirety.app.domain.User domainUser = userDAO.getUser(login);

        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;

        return new User(
                domainUser.getLogin(),
                domainUser.getPassword(),
                enabled,
                accountNonExpired,
                credentialsNonExpired,
                accountNonLocked,
                getAuthorities(domainUser.getAuthority().getId())
        );
    }

    public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
        List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
        return authList;
    }

    public List<String> getRoles(Integer role) {

        List<String> roles = new ArrayList<String>();

        if (role.intValue() == 1) {
            roles.add("ROLE_MODERATOR");
            roles.add("ROLE_ADMIN");
        } else if (role.intValue() == 2) {
            roles.add("ROLE_MODERATOR");
        }
        return roles;
    }

    public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}

没有逻辑上的原因可以解释为什么它会失败,但它确实失败了。

注意:

我的IDE可以链接自动装配(也就是说,它知道它正在从哪里进行自动装配等),但编译失败了。

编辑2: 我已经从SecurityConfig.java文件中删除了以下代码

@Autowired
private UserDataService userDataService;

我将其添加到我知道会定期调用和包含其他服务的POJO和控制器之一中。在这种情况下,我将该代码粘贴到了我的baseController.java文件中,该文件指导呈现主页。该服务已经编译正确,并且我甚至可以在控制器内部调用它。

所以问题只隔离到配置文件,比如SecurityConfig.java,在那时它就不能工作了。

编辑3:添加额外的文件

SecurityInitializer.java

@Order(2)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer  {
}

WebInitializer.java

@Order(1)
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { PersistanceConfig.class, SecurityConfig.class };  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

//    @Override
//    protected Filter[] getServletFilters() {
//        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
//        characterEncodingFilter.setEncoding("UTF-8");
//        return new Filter[] { characterEncodingFilter};
//    }
}

mvc-dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.entirety.app"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

web.xml

<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Spring MVC Application</display-name>

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3
UserDetailsServiceImplementation 是否被扫描到了? - Dirk Lachowski
UserDetailsService类在CrmUserServiceSecurityConfig中是否属于同一个包(UserDetailsService是一个非常常见的名称,你可能从两个不同的包中导入了它们。) - Ralph
我已将其修改为CrmUserService,仅仅是为了测试是否由于代码冲突(如编辑所反映的)而导致没有运行成功。 - Aeseir
你在beans.xml文件中也定义了这个吗? - Keerthivasan
你有一个 @Service,但我在你的 @Configuration 类中没有看到 @ComponentScan。如果没有组件扫描,你的组件将永远不会被初始化。 - M. Deinum
显示剩余7条评论
4个回答

15

更新(提供完整示例后)

看起来您有多个问题。

web.xml vs AbstractAnnotationConfigDispatcherServletInitializer

您创建的应用程序具有一个称为 mvc-dispatcher 的 DispatcherServlet 的 web.xml。 mvc-dispatcher 具有 mvc-dispatcher-servlet.xml 的配置,该文件加载 com.springapp.sectest 包中的所有 bean。这意味着 mvc-dispatcher 可以找到您的 UserDetailsService

该应用程序还具有 AbstractAnnotationConfigDispatcherServletInitializer,它创建了一个加载您的 WebConfig Java 配置的 DispatcherServlet。这个 DispatcherServlet 无法看到由 mvc-dispatcher 创建的任何 Bean。

简而言之,您应该使用 web.xml 或 AbstractAnnotationConfigDispatcherServletInitializer 中的 DispatcherServlet,而不是同时使用两者。

getRootConfigClasses vs getServletConfigClasses

在 getRootConfigClasses 中的任何配置通常称为根或父配置,不能查看在 getServletConfigClasses 中定义的 Bean。 Spring Security 使用名为 springSecurityFilterChain 的过滤器保护在 getRootConfigClasses 中定义的应用程序,该过滤器由 @EnableWebSecurity 注释创建。这意味着通常最好将 Spring Security 的配置放在 getRootConfigClasses 中。但也有一些例外,例如如果您要在 Spring MVC 控制器上执行方法安全性。

在 getServletConfigClasses 中的任何配置通常称为子配置,并且可以查看在 getRootConfigClasses 中定义的 Bean。getServletConfigClasses 配置 DispatcherServlet,必须包含 Spring MVC 的 Bean(即 Controllers、ViewResovlers 等)。在 getRootConfigClasses 中定义的任何 Spring MVC Bean 都是可见但未使用的。

虽然这种设置有点令人困惑,但允许您具有具有隔离配置的多个 DispatcherServlet 实例。实际上,拥有多个 DispatcherServlet 实例非常罕见。

基于上述信息,您应该将 UserDetailsService 声明以及所有依赖项 Bean 移动到 getRootConfigClasses 中。这将确保 SecurityConfig.java 可以找到 UserDetailsService

修复的拉取请求

我已经提交了一个拉取请求,以便使您的应用程序启动时没有错误 https://github.com/worldcombined/AnnotateFailed/pull/1。几个注意事项:

  • 测试未使用父子上下文,因此不反映应用程序运行的情况
  • 我必须移动资源文件夹,以便正确地捕获它们
  • 我没有使您能够进行身份验证。没有 login.jsp 并且您提供的 UserDetailsService 总是返回 null。相反,我试图证明这里的错误已经得到解答。

原始回答

根据此评论:

 

@ComponentScan 在我的 mvc-dispatcher-servlet.xml 中执行,   <context:component-scan base-package="com.entirety.app"/>

看起来您正在配置 Dispatcher 配置中的服务,而不是在根上下文中配置。

Spring Security 的配置通常在根上下文中进行配置。例如,可以扩展

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

public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {
}
你是如何导入安全配置的?如果Spring Security配置在根上下文中,则不会看到在Dispatcher Servlet上下文中定义的bean。
解决方案是将服务配置移动到根上下文中或将Spring Security配置移动到Dispatcher的ApplicationContext中。例如,如果您的Dispatcher Servlet命名为mvc,则将Spring Security配置移动到dispatcher上下文中的示例如下。
public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {
    protected String getDispatcherWebApplicationContextSuffix() {
        return "mvc";
    }
}

是的,我已经把所有东西都整理好了。如评论中所提到的,我在这个应用程序中使用注释而不是XML。因此,一切都按照注释方法设置。例如,对于我拥有(或接近)的结构,您可以看到YummyNoddleBar演示Spring的结构。我有相同的结构。除了我正在使用客户用户服务。 - Aeseir
我猜我错过了你说一切都正常工作的评论。如果你已经解决了问题,你应该将一个答案标记为被接受的。 - Rob Winch
谢谢Rob。我已经尝试了Indy的想法,但不幸的是它并没有起作用。我会尝试提取一些你的材料并测试一下。我有一种直觉,基于注释的安全性可能存在漏洞,我将创建一个全新的骨架应用程序并附上Github链接,你可以试试看并让我知道问题可能出在哪里。这很奇怪。 - Aeseir
请创建一个样例项目。我计划在周一发布3.2.1版本,所以您能尽早提供样例,任何必要的修复就越有可能在该版本中实现。 - Rob Winch
嗨,Rob,谢谢你的帮助。在根据你的建议修改了代码后,我找到了整个问题的根本原因。除非你专门扫描那个UserDetailsService实现(在我的情况下是UserAccessDetails和UserAccessDetails.java中的Autowired存储库),否则你会遇到注入问题。你是否应该只为那个Config指定特定的类扫描呢?(希望这样说得清楚) - Aeseir
显示剩余9条评论

0

它应该按类型自动装配,但是...

你把你的UserDetailService命名了

@Service("userService")

尝试将您的字段命名为相同的名称

@Autowired
private UserDetailsService userService;

经过多次尝试,发现这个方法并不适用。之前还有其他人也推荐过它,但后来他们修改了评论。这让我觉得可能是与Spring Security核心的UserDetailsService有关的问题。 - Aeseir
在您的bean中添加@PostConstruct方法,检查它是否被创建。在启动应用程序时,您还应该获取Spring日志-它显示已创建的bean。您还可以自动装配ApplicationContext ctx;并查看ctx.getBeanDefinitionNames()。 - hi_my_name_is
谢谢,我已经检查过了,但是无法编译它,因为它会抛出错误,但我的IDE仍然可以识别这个bean。 - Aeseir

0

在 SecurityConfig 类中使用 @component


2
这就是为什么帮助比@Configuration更好的原因? - Dirk Lachowski
@Component已经过时,因为出现了特定的注解。 - Aeseir

0
如果尝试将此注释添加到SecurityConfig类中会怎样呢?
@ComponentScan(basePackages =“包含UserDetailsService的包”)

扫描工作正常。如上所述,我在同一目录中有6个其他服务,所有服务都正常工作。错误似乎来自于Spring Security UserDetails而不是我的代码的个人实现。 - Aeseir
在这个教程中,我们可以学到如何配置Spring Security来保护密码。在示例中,authenticationManager包含以下内容:<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="jdbcUserService"> <password-encoder ref="passwordEncoder"> <salt-source ref="saltSource"/> </password-encoder> </authentication-provider> </authentication-manager> - indybee
我认为这是正确的解决方案。扫描正在子上下文中进行,应该在父上下文中进行。从mvc配置中删除扫描并添加此注释。 - Rob Winch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接