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/**")); } }
关键注意事项
- 移除旧版
jwtAuthFilterRegisterBean:Spring Security 6中加入安全链的过滤器不会自动注册到Servlet容器,无需手动禁用 EnableGlobalMethodSecurity已替换为EnableMethodSecurity,新版配置中已正确使用- 确保
AzureADUtils的令牌校验逻辑与Spring Security 6上下文兼容,避免SecurityContextHolder线程安全问题
内容的提问来源于stack exchange,提问作者NLearning
相关产品推荐
相关产品推荐

