一、问题
SpringSecurity自定义账号密码校验,启动报错,出现依赖循环。
二、大致背景
SpringBoot版本:2.6.14,基于SpringSecurity实现自定义账号密码校验登录功能。SpringSecurity的配置类SecurityConfig配置了自定义的账号密码校验器UsernamePasswordAuthenticationProvider,同时还配置了AuthenticationManager以及PasswordEncoder等。UsernamePasswordAuthenticationProvider主要功能为基于配置的PasswordEncoder(BCryptPasswordEncoder)实现自定义的账号密码校验。
(1)SecurityConfig
(2)UsernamePasswordAuthenticationProvider
三、具体报错
项目启动失败,报错Spring循环依赖
(1)报错分析
根据错误信息所提到的依赖:
1.loginController 依赖于 LoginService(loginServiceImpl 的实例)。
2.loginServiceImpl 依赖于 AuthenticationManager。
3.SecurityConfig(配置类)中定义了 AuthenticationManager(通常是通过配置方法隐式或显式地创建的),并且它也依赖于 UsernamePasswordAuthenticationProvider。
4.UsernamePasswordAuthenticationProvider 又依赖于 PasswordEncoder。
然而,错误描述中并没有明显的依赖循环,但可能的问题在于Spring Security的配置方式或Bean的创建顺序上。
(2)问题定位与分析
从错来看,循环应该发生在SecurityConfig与UsernamePasswordAuthenticationProvider之间。
SecurityConfig配置了UsernamePasswordAuthenticationProvider作为自定义的账号密码校验器,UsernamePasswordAuthenticationProvider仅依赖于UserDetailsServiceImpl和PasswordEncoder。
UserDetailsServiceImpl与SecurityConfig没有直接依赖关系,在SecurityConfig中passwordEncoder()方法使用@Bean注解,将BCryptPasswordEncoder的实例注册为一个bean。当应用程序上下文启动时,Spring会根据配置信息创建所有的bean实例。对于这些组件来说,如果Spring首先创建SecurityConfig类的实例,并调用其中的passwordEncoder()方法。在passwordEncoder()方法中,会创建并返回一个BCryptPasswordEncoder实例,并将其注册为一个bean。而SecurityConfig依赖于UsernamePasswordAuthenticationProvider,UsernamePasswordAuthenticationProvider有依赖于PasswordEncoder的实例BCryptPasswordEncoder。从而形成了循环依赖!!!
四、解决问题
(1)使用构造函数注入
将其中一个类的依赖关系移至构造函数,而不是通过字段注入。 将PasswordEncoder 通过构造函数new一个对象给UsernamePasswordAuthenticationProvider,而不是通过字段注入。这样,可以确保在 SecurityConfig 实例化之前,UsernamePasswordAuthenticationProvider 的依赖 PasswordEncoder 已经被满足。
代码:
//使用构造函数注入PasswordEncoder
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
(2)使用 @Lazy注解
使用 @Lazy 注解可以延迟创建 bean 的实例,从而打破循环依赖。在 UsernamePasswordAuthenticationProvider类中对 PasswordEncoder的注入点使用 @Lazy 注解。这将使得 UsernamePasswordAuthenticationProvider 在首次使用PasswordEncoder时才PasswordEncoder被实例化,从而解决循环依赖问题。
代码:
//使用 @Lazy注解延迟创建 PasswordEncoder 的实例
@Autowired
@Lazy
private PasswordEncoder passwordEncoder;
实例
@Autowired
@Lazy
private PasswordEncoder passwordEncoder;