如何在 Spring 引导中控制 Bean 创建和组件扫描的顺序
How to Control Sequence of Bean Creating and Component Scanning In Spring Boot
已更新
我是 Spring 的初学者,我尝试使用基于 Java 的配置来实施 spring 安全应用程序。但是现在我必须控制 bean 创建的顺序和应用程序的组件扫描。
这是我的配置class
@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);
}
}
这工作正常。
但是一切都出错了当我尝试将 JWTLoginFilter
声明为带有 @Service
注释的服务时,当我试图自动装配它时。
我所做的更改如下。
这是配置class。
@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
和 [ 扩展的 class 中创建=93=] 允许一个 WebSecurityConfigurerAdapter
扩展 class。所以我无法在另一个 class 中启动它。
还有
@Override
protected void configure(HttpSecurity http) throws Exception {}
必须在 WebSecurityConfigurerAdapter
扩展 class 中声明并且此方法使用 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 创建和组件扫描的顺序。有人有想法吗?请帮助我。
已更新-->
我尝试按如下方式实现 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)
└─────┘
我认为问题是,如果我们像上面那样自动连接构造函数,那么 JWTLoginFilter
不能在不创建 Configuration
beans 的情况下创建。但是配置 bean 需要 JWTLoginFilter
bean。所以它不能在没有 JWTLoginFilter bean 的情况下创建。
谢谢。
Spring Boot有多个条件注解可以像@ConditionalOnBean一样使用来控制bean创建的顺序
查看软件包 org.springframework.boot.autoconfigure.condition 以获得所有可用的条件
对于您的示例,最好的方法是在 JWTLoginFilter
中注入 AuthenticationManager 的构造函数
@Autowired
注解将在 之后 bean 的构造函数被调用。所以你的异常不依赖于 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();
}
...
}
已更新
我是 Spring 的初学者,我尝试使用基于 Java 的配置来实施 spring 安全应用程序。但是现在我必须控制 bean 创建的顺序和应用程序的组件扫描。
这是我的配置class
@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);
}
}
这工作正常。
但是一切都出错了当我尝试将 JWTLoginFilter
声明为带有 @Service
注释的服务时,当我试图自动装配它时。
我所做的更改如下。
这是配置class。
@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
和 [ 扩展的 class 中创建=93=] 允许一个 WebSecurityConfigurerAdapter
扩展 class。所以我无法在另一个 class 中启动它。
还有
@Override
protected void configure(HttpSecurity http) throws Exception {}
必须在 WebSecurityConfigurerAdapter
扩展 class 中声明并且此方法使用 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 创建和组件扫描的顺序。有人有想法吗?请帮助我。
已更新-->
我尝试按如下方式实现 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)
└─────┘
我认为问题是,如果我们像上面那样自动连接构造函数,那么 JWTLoginFilter
不能在不创建 Configuration
beans 的情况下创建。但是配置 bean 需要 JWTLoginFilter
bean。所以它不能在没有 JWTLoginFilter bean 的情况下创建。
谢谢。
Spring Boot有多个条件注解可以像@ConditionalOnBean一样使用来控制bean创建的顺序
查看软件包 org.springframework.boot.autoconfigure.condition 以获得所有可用的条件
对于您的示例,最好的方法是在 JWTLoginFilter
中注入 AuthenticationManager 的构造函数
@Autowired
注解将在 之后 bean 的构造函数被调用。所以你的异常不依赖于 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();
}
...
}