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

Spring Security 5.x迁移至6.x时如何配置userDetailsService

Spring Boot 3/Spring Security 6迁移:解决UserDetailsService配置错误及自定义JWT过滤器适配

问题核心

从Spring Boot 2.x/Spring Security 5.x迁移到3.x/6.x时,原WebSecurityConfigurerAdapter类被废弃,导致旧版通过configure(AuthenticationManagerBuilder)绑定UserDetailsService与PasswordEncoder的方式失效,引发运行时配置错误;同时自定义JWT过滤器未正确适配新版安全链逻辑,失去令牌校验能力。

解决方案

1. 修复UserDetailsService配置

在Spring Security 6中,需通过AuthenticationProvider和AuthenticationManager Bean替代旧版的configure(AuthenticationManagerBuilder)方法,完成UserDetailsService与密码编码器的绑定:

@Bean
public AuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());
    return authProvider;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
    return authConfig.getAuthenticationManager();
}

2. 还原自定义JWT过滤器逻辑

新版JwtAuthenticationTokenFilter必须继承OncePerRequestFilter,并保留旧版的Azure令牌校验逻辑:

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private final Log logger = LogFactory.getLog(this.getClass());
    public static final String UNAUTHORIZED = "Unauthorized";
    private final UserDetailsService userDetailsService;
    private final AzureADUtils azureADUtils;

    public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, AzureADUtils azureADUtils) {
        this.userDetailsService = userDetailsService;
        this.azureADUtils = azureADUtils;
    }

    private int tokenType(HttpServletRequest request) {
        int tokenType = 0;
        final String requestHeader = request.getHeader(SecurityConstants.HEADER_STRING);
        final String apiKeyHeader = request.getHeader(SecurityConstants.HEADER_TOKEN_STRING);

        if (requestHeader != null && requestHeader.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            tokenType = 1; // AZURE TOKEN
        }
        return tokenType;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String requestHeader = request.getHeader(SecurityConstants.HEADER_STRING);
        String rolename = null;
        String authToken = null;
        int type = tokenType(request);
        boolean UNAUTHORIZED_ERROR = false;

        switch (type) {
            case 0:
                logger.warn("Ignoring other headers");
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, UNAUTHORIZED);
                UNAUTHORIZED_ERROR = true;
                break;
            case 1:
                requestHeader = request.getHeader(SecurityConstants.HEADER_STRING);
                authToken = requestHeader.substring(7);
                try {
                    rolename = azureADUtils.getRolenameFromToken(authToken);

                    if (!SecurityConstants.ROLE_NAME.equalsIgnoreCase(rolename)) {
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, UNAUTHORIZED);
                        UNAUTHORIZED_ERROR = true;
                    }
                } catch (TokenException e) {
                    logger.warn("the token is expired and not valid anymore", e);
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, UNAUTHORIZED);
                    UNAUTHORIZED_ERROR = true;
                }
                break;
        }

        if (rolename != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            if (type == 1) {
                boolean istokenValid = false;
                try {
                    istokenValid = azureADUtils.validateToken(authToken);
                } catch (Exception e) {
                    logger.error("Error in token validation : " + e.getMessage());
                }
                if (!istokenValid) {
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, UNAUTHORIZED);
                    UNAUTHORIZED_ERROR = true;
                }
            }
        }

        if (!UNAUTHORIZED_ERROR)
            chain.doFilter(request, response);
    }
}

3. 完善SecurityFilterChain配置

将自定义过滤器加入安全链,同时保留原有CORS、CSRF、端点权限等配置:

@Configuration
@EnableWebSecurity
@EnableAsync
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig {

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private AzureADUtils azureADUtils;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter(userDetailsService, azureADUtils);
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .csrf(csrf -> csrf.disable())
                .authenticationProvider(authenticationProvider())
                .authorizeHttpRequests(authz -> authz
                        .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        .anyRequest().authenticated())
                .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        // 保留旧版Header配置
        http.headers(headers -> headers
                .frameOptions(frame -> frame.sameOrigin())
                .httpStrictTransportSecurity(hsts -> hsts.disable()));

        return http.build();
    }

    // 自定义CORS配置(可选)
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/getMIToken/**"));
    }
}

关键注意事项

  • 移除旧版jwtAuthFilterRegister Bean:Spring Security 6中加入安全链的过滤器不会自动注册到Servlet容器,无需手动禁用
  • EnableGlobalMethodSecurity已替换为EnableMethodSecurity,新版配置中已正确使用
  • 确保AzureADUtils的令牌校验逻辑与Spring Security 6上下文兼容,避免SecurityContextHolder线程安全问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 15:30:55