Spring Security中同时注册Dao与自定义PhraseAuthenticationProvider的问题
问题场景与解决方案
需求
- 客户端发送用户名密码时,使用
DaoAuthenticationProvider完成认证 - 客户端请求头携带
phrase时,使用自定义的PhraseAuthenticationProvider完成认证
已完成操作
- 实现
UserDetailsService接口为CustomSecurityCustomerService并标注@Service注解 - 在安全配置类
ProjectSecurityConfigurer中配置DaoAuthenticationProviderBean - 实现继承
AbstractAuthenticationToken的PhraseAuthenticationToken - 实现自定义认证提供者
PhraseAuthenticationProvider并标注@Component
遇到的问题
手动配置DaoAuthenticationProvider Bean后,自定义的PhraseAuthenticationProvider无法被注册生效;只有注释掉DaoAuthenticationProvider的Bean配置时,自定义认证提供者才能正常工作。需实现两者同时注册,并根据请求头触发对应认证逻辑。
解决方案
核心问题是:Spring Security默认会自动注册DaoAuthenticationProvider,但手动定义该Bean后,默认自动注册逻辑会被覆盖,且自定义PhraseAuthenticationProvider需要手动添加到SecurityFilterChain中。同时需确保认证过滤器能根据请求头选择对应认证方式。
1. 调整安全配置类,同时注册两个认证提供者
修改ProjectSecurityConfigurer,将手动配置的DaoAuthenticationProvider和自定义PhraseAuthenticationProvider都添加到HttpSecurity中,确保两者都被认证管理器加载。
2. 完善认证过滤器逻辑
让ExtractionFilter根据请求头是否存在phrase,决定生成PhraseAuthenticationToken触发自定义认证,或跳过当前过滤器,交给后续BasicAuthenticationFilter处理用户名密码认证。
修改后的代码
安全配置类代码
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.config.Customizer; @EnableWebSecurity // 必须添加此注解,否则Spring Security不会加载该配置 public class ProjectSecurityConfigurer{ @Autowired private AuthenticationConfiguration config; @Autowired PhraseAuthenticationProvider pProvider; @Autowired UserDetailsService customerSecurityService; @Autowired PasswordEncoder passwordEncoder; @Bean ExtractionFilter getExFilter() throws Exception { return new ExtractionFilter(config.getAuthenticationManager()); } @Bean SecurityFilterChain projectSecSpecs(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .antMatchers("/myaccount").authenticated() .antMatchers("/contact","/login").permitAll()) .httpBasic(Customizer.withDefaults()) .addFilterBefore(getExFilter(), BasicAuthenticationFilter.class) // 同时添加两个认证提供者 .authenticationProvider(pProvider) .authenticationProvider(getDaoBean()); return http.build(); } @Bean DaoAuthenticationProvider getDaoBean() { DaoAuthenticationProvider daoProvider= new DaoAuthenticationProvider(); daoProvider.setUserDetailsService(customerSecurityService); daoProvider.setPasswordEncoder(passwordEncoder); return daoProvider; } @Bean PasswordEncoder encoder() { return NoOpPasswordEncoder.getInstance(); } }
完善后的ExtractionFilter代码
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import java.io.IOException; public class ExtractionFilter extends AbstractAuthenticationProcessingFilter { public ExtractionFilter(AuthenticationManager authenticationManager) { super(request -> true); // 匹配所有请求,可根据实际需求调整匹配规则 setAuthenticationManager(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String phrase = request.getHeader("phrase"); if (phrase != null && !phrase.isEmpty()) { // 请求头存在phrase,生成自定义Token并提交认证 PhraseAuthenticationToken authRequest = new PhraseAuthenticationToken(phrase, null); return getAuthenticationManager().authenticate(authRequest); } // 无phrase请求头,跳过当前过滤器,交给BasicAuthenticationFilter处理用户名密码认证 return null; } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(authResult); chain.doFilter(request, response); } @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); super.unsuccessfulAuthentication(request, response, failed); } }
关键说明
- @EnableWebSecurity注解:必须添加到安全配置类,否则Spring Security不会识别并加载该配置。
- 双认证提供者注册:通过
http.authenticationProvider()方法分别添加两个认证提供者,确保两者都被纳入认证管理器的候选列表。 - 过滤器分支逻辑:
ExtractionFilter通过请求头判断认证路径,避免两种认证逻辑冲突。 - supports方法匹配:
PhraseAuthenticationProvider的supports方法已正确匹配PhraseAuthenticationToken,认证管理器会自动根据Token类型选择对应提供者。
内容的提问来源于stack exchange,提问作者Sidharth M
相关产品推荐
相关产品推荐

