You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security中同时注册Dao与自定义PhraseAuthenticationProvider的问题

问题场景与解决方案

需求

  • 客户端发送用户名密码时,使用DaoAuthenticationProvider完成认证
  • 客户端请求头携带phrase时,使用自定义的PhraseAuthenticationProvider完成认证

已完成操作

  • 实现UserDetailsService接口为CustomSecurityCustomerService并标注@Service注解
  • 在安全配置类ProjectSecurityConfigurer中配置DaoAuthenticationProvider Bean
  • 实现继承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);
    }
}

关键说明

  1. @EnableWebSecurity注解:必须添加到安全配置类,否则Spring Security不会识别并加载该配置。
  2. 双认证提供者注册:通过http.authenticationProvider()方法分别添加两个认证提供者,确保两者都被纳入认证管理器的候选列表。
  3. 过滤器分支逻辑:ExtractionFilter通过请求头判断认证路径,避免两种认证逻辑冲突。
  4. supports方法匹配:PhraseAuthenticationProvider的supports方法已正确匹配PhraseAuthenticationToken,认证管理器会自动根据Token类型选择对应提供者。

内容的提问来源于stack exchange,提问作者Sidharth M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 17:37:02