首页 文章

如何在Spring Boot中控制Bean创建和组件扫描的顺序

提问于
浏览
0

Updated

我是Spring的初学者,我尝试使用基于Java的配置实现spring安全性应用程序 . 但是现在我必须控制bean的创建序列和应用程序的组件扫描 .

这是我的配置类

@EnableWebSecurity
@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public Md5PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().cacheControl();
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/authProxy").permitAll()
                .antMatchers(HttpMethod.POST,"/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JWTLoginFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

这是 JWTLoginFilter

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    private TokenAuthenticationService tokenAuthenticationService;

    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher("/login"));
        setAuthenticationManager(authenticationManager);
        tokenAuthenticationService = new TokenAuthenticationService();
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws AuthenticationException, IOException, ServletException {

        AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);

        final Authentication authentication = getAuthenticationManager()
                .authenticate(new UsernamePasswordAuthenticationToken(credentials.getUsername(),
                        credentials.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
        return getAuthenticationManager().authenticate(token);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
            throws IOException, ServletException {
        String name = authentication.getName();
        tokenAuthenticationService.addAuthentication(response, name);
    }
}

这工作正常 . 但是当我尝试使用 @Service 注释声明 JWTLoginFilter 作为服务并且我正在尝试自动装配时,一切都会出错 .

我所做的改变如下 .

这是配置类 .

@EnableWebSecurity
@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public Md5PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();
    }

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

    @Autowired
    JWTLoginFilter jwtLoginFilter;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().cacheControl();
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/authProxy").permitAll()
                .antMatchers(HttpMethod.POST,"/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

这是我的新 JWTLoginFilter

@Service
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    @Autowired
    AuthenticationManager authenticationManager;

    private TokenAuthenticationService tokenAuthenticationService;

    public JWTLoginFilter() {
        super(new AntPathRequestMatcher("/login"));
        setAuthenticationManager(authenticationManager);
        tokenAuthenticationService = new TokenAuthenticationService();
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws AuthenticationException, IOException, ServletException {

        AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);

        final Authentication authentication = getAuthenticationManager()
                .authenticate(new UsernamePasswordAuthenticationToken(credentials.getUsername(),
                        credentials.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
        return getAuthenticationManager().authenticate(token);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
            throws IOException, ServletException {
        String name = authentication.getName();
        tokenAuthenticationService.addAuthentication(response, name);
    }
}

此代码会调用运行时错误

Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error creating bean with name 'JWTLoginFilter' defined in file [/media/dilanka/Stuff/CODEBASE/Inspection-Application/Inspection-AuthProxy/target/classes/com/shipxpress/inspection/security/jwt/JWTLoginFilter.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: authenticationManager must be specified

这个错误就像我在开始时的想法一样, ComponentScan 扫描并启动 JWTLoginFilter . 但那时 AuthenticationManager bean还没有创建 . 所以它不是自动接线 .

所以我必须在扫描 JWTLoginFilter 之前创建 AuthenticationManager bean,但这是不可能的,因为它必须在类中创建由 WebSecurityConfigurerAdapter 扩展而spring允许一个 WebSecurityConfigurerAdapter 扩展类 . 所以我不能在另一堂课中发起它 . 也

@Override
        protected void configure(HttpSecurity http) throws Exception {}

必须在 WebSecurityConfigurerAdapter 扩展类中声明,此方法使用 jwtLoginFilter . 所以

@Autowired
    JWTLoginFilter jwtLoginFilter;

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

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().cacheControl();
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/authProxy").permitAll()
                .antMatchers(HttpMethod.POST,"/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

必须在 WebSecurityConfig extends WebSecurityConfigurerAdapter class 中定义它,并且必须控制应用程序的bean创建和组件扫描序列 . 有没有人有想法?请帮我 .

updated-->

我试图按如下方式实现JWTLoginFilter,

@Service
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    private TokenAuthenticationService tokenAuthenticationService;

    @Autowired
    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher("/login"));
    }
...
}

但它给出了以下错误

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  JWTLoginFilter defined in file [/media/dilanka/Stuff/CODEBASE/Inspection-Application/DR-136812421-dbchangesSendAsMail/Inspection-Application/Inspection-AuthProxy/target/classes/com/shipxpress/inspection/security/jwt/JWTLoginFilter.class]
↑     ↓
|  webSecurityConfig (field com.shipxpress.inspection.security.jwt.JWTLoginFilter com.shipxpress.inspection.config.WebSecurityConfig.jwtLoginFilter)
└─────┘

我认为问题是,如果我们自动连接Constructor如上所述,那么 JWTLoginFilter 无法创建而不创建 Configuration beans创建 . 但是配置bean需要 JWTLoginFilter bean . 所以没有JWTLoginFilter bean就无法创建 .

谢谢 .

2 回答

  • 2

    调用bean的构造函数后将处理 @Autowired 注释 . 因此,您的异常不依赖于bean创建的顺序 . 如果需要从构造函数中调用 setAuthenticationManager ,可以将 @Autowired 应用于构造函数:

    @Service
    public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
    
        AuthenticationManager authenticationManager;    
        private TokenAuthenticationService tokenAuthenticationService;
    
        @Autowired
        public JWTLoginFilter(AuthenticationManager authenticationManager) {
            this.authenticationManager = authenticationManager; //if you will need this instance in future
            super(new AntPathRequestMatcher("/login"));
            setAuthenticationManager(authenticationManager);
            tokenAuthenticationService = new TokenAuthenticationService();
        }
    
        ...
    }
    

    然后适当的bean将自动传递给构造函数 .

    另一种解决方案是在 @PostConstruct 方法中进行所有初始化 . 在处理 @Autowired 注释后立即调用此方法:

    @Service
    public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
    
        @Autowired
        AuthenticationManager authenticationManager;    
        private TokenAuthenticationService tokenAuthenticationService;
    
        public JWTLoginFilter(){
            super(new AntPathRequestMatcher("/login"));
        }
    
        @PostConstruct
        public void postConstruct() {
            setAuthenticationManager(authenticationManager);
            tokenAuthenticationService = new TokenAuthenticationService();
        }
    
        ...
    }
    
  • 0
    • Spring Boot有多个条件注释用于@ConditionalOnBean来控制bean创建的顺序

    • 查看所有可用条件的org.springframework.boot.autoconfigure.condition包

    • 对于您的示例,最好的方法是在JWTLoginFilter中对AuthenticationManager进行构造函数注入

相关问题