Spring Boot 2迁移至3:LDAP与API安全配置异常问题
Spring Boot 2迁移至Spring Boot 3的安全配置问题
我正在将Spring Boot 2项目迁移至Spring Boot 3,安全配置遇到问题。应用包含需要LDAP认证的Web应用,以及需要HTTP Basic认证的API;预期/api路径跳转到Swagger界面并启用HTTP Basic认证,但所有URL都被重定向到登录页。
相关配置代码
WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig { @Configuration @Order(1) public static class ApiWebSecurityConfigurationAdapter { @Autowired CustomAuthenticationProvider customAuthenticationProvider; @Bean protected SecurityFilterChain apiSecurityFilterChain(final HttpSecurity http) throws Exception { http.securityMatcher("/api/**").authorizeHttpRequests(auth -> { auth.anyRequest().authenticated(); }); http.headers(header -> header.frameOptions().disable()); http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.httpBasic(withDefaults()); return http.build(); } @Autowired public void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(customAuthenticationProvider); } } @Configuration @Order(2) public static class FormLoginWebSecurityConfigurerAdapter { @Autowired UserService userDetailsService; @Value("${ldap.urls}") private String ldapUrls; @Value("${ldap.base.dn}") private String ldapBaseDn; @Value("$ldap.search.ou") private String ldapSearchOu; @Value("${ldap.username}") private String ldapSecurityPrincipal; @Value("${ldap.password}") private String ldapPrincipalPassword; @Value("${ldap.user.dn.pattern}") private String ldapUserDnPattern; private static final String[] AUTH_WHITELIST = {"/swagger-resources/**", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs", "/webjars/**", "/api"}; @Bean protected SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth.shouldFilterAllDispatcherTypes(true).requestMatchers("/login*").permitAll() .requestMatchers(AUTH_WHITELIST).permitAll().requestMatchers("/").authenticated()) .formLogin( login -> login.loginPage("/login").loginProcessingUrl("/login").failureUrl("/login?error")) .logout(logout -> logout.invalidateHttpSession(false).clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login?logout").permitAll()); http.headers(header -> header.frameOptions().disable()); http.sessionManagement(session -> session.maximumSessions(1).and().invalidSessionUrl("/login?expired")); return http.build(); } @Autowired public void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.ldapAuthentication().ldapAuthoritiesPopulator(new CustomLdapAuthoritiesPopulator(userDetailsService)) .userSearchFilter(ldapUserDnPattern).groupSearchBase(ldapSearchOu).contextSource() .url(ldapUrls + ldapBaseDn).managerDn(ldapSecurityPrincipal).managerPassword(ldapPrincipalPassword); } } }
UserService.java
@Service public class UserService implements UserDetailsService { private final UtilisateurRepository utilisateurRepository; public UserService(final UtilisateurRepository utilisateurRepository) { this.utilisateurRepository = utilisateurRepository; } /** * loadUserByUsername. * * @param username Le nom d'utilisateur * @return L'utilisateur */ @Override public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { final UtilisateurDao utilisateurDao = utilisateurRepository.findByLogin(username); return new User(utilisateurDao.getMatricule().toString(), null, null); } /** * loadUserAuthorities. * * @param username Le nom d'utilisateur * @return Le profil de l'utilisateur */ public List<GrantedAuthority> loadUserAuthorities(final String username) { final UtilisateurDao utilisateurDao = utilisateurRepository.findByLogin(username); if (utilisateurDao != null && utilisateurDao.getProfil() != null) { return AuthorityUtils.createAuthorityList(utilisateurDao.getProfil()); } return new ArrayList<GrantedAuthority>(); } }
日志信息
2023-07-13T09:15:52.921+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-3][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Securing GET / [2m2023-07-13T09:15:52.929+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-3][0;39m [36mo.s.s.w.a.AnonymousAuthenticationFilter [0;39m [2m:[0;39m Set SecurityContextHolder to anonymous SecurityContext [2m2023-07-13T09:15:52.930+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-3][0;39m [36mo.s.s.w.session.SessionManagementFilter [0;39m [2m:[0;39m Request requested invalid session id 87847793E2A3E57F9B41DE144174BE4D [2m2023-07-13T09:15:52.930+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-3][0;39m [36mw.s.SimpleRedirectInvalidSessionStrategy[0;39m [2m:[0;39m Starting new session (if required) and redirecting to '/login?expired' [2m2023-07-13T09:15:52.976+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-3][0;39m [36mo.s.s.web.DefaultRedirectStrategy [0;39m [2m:[0;39m Redirecting to /login?expired [2m2023-07-13T09:15:52.985+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-2][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Securing GET /login?expired [2m2023-07-13T09:15:52.986+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-2][0;39m [36mo.s.s.w.a.AnonymousAuthenticationFilter [0;39m [2m:[0;39m Set SecurityContextHolder to anonymous SecurityContext [2m2023-07-13T09:15:52.987+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-2][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Secured GET /login?expired [2m2023-07-13T09:16:16.945+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Securing GET /api [2m2023-07-13T09:16:16.948+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36mo.s.s.w.a.AnonymousAuthenticationFilter [0;39m [2m:[0;39m Set SecurityContextHolder to anonymous SecurityContext [2m2023-07-13T09:16:16.949+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36ms.w.a.DelegatingAuthenticationEntryPoint[0;39m [2m:[0;39m Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest] [2m2023-07-13T09:16:16.950+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36ms.w.a.DelegatingAuthenticationEntryPoint[0;39m [2m:[0;39m No match found. Using default entry point org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint@ffd6833 [2m2023-07-13T09:16:16.956+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Securing GET /error [2m2023-07-13T09:16:16.958+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36mo.s.s.w.a.AnonymousAuthenticationFilter [0;39m [2m:[0;39m Set SecurityContextHolder to anonymous SecurityContext [2m2023-07-13T09:16:16.962+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36mo.s.s.w.s.HttpSessionRequestCache [0;39m [2m:[0;39m Saved request http://localhost:8081/error?continue to session [2m2023-07-13T09:16:16.963+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-4][0;39m [36mo.s.s.web.DefaultRedirectStrategy [0;39m [2m:[0;39m Redirecting to http://localhost:8081/login [2m2023-07-13T09:16:16.968+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-1][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Securing GET /login [2m2023-07-13T09:16:16.968+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-1][0;39m [36mo.s.s.w.a.AnonymousAuthenticationFilter [0;39m [2m:[0;39m Set SecurityContextHolder to anonymous SecurityContext [2m2023-07-13T09:16:16.969+02:00[0;39m [32mDEBUG[0;39m [35m5028[0;39m [2m---[0;39m [2m[nio-8081-exec-1][0;39m [36mo.s.security.web.FilterChainProxy [0;39m [2m:[0;39m Secured GET /login
问题分析与修复方案
核心问题点
/api路径匹配范围不足:API配置的securityMatcher("/api/**")仅匹配/api开头的子路径,不匹配/api本身,导致/api请求被FormLogin的规则处理。- LDAP属性注入错误:
@Value("$ldap.search.ou")缺少大括号,应为@Value("${ldap.search.ou}"),会导致LDAP配置加载失败。 - 认证错误页被拦截:访问
/api未认证时触发Basic认证返回401,Spring Boot自动转发到/error,而/error不在API的匹配范围内,被FormLogin规则重定向到登录页。 - UserDetails构造不合法:
new User(..., null, null)传入空密码和空权限,不符合Spring Security的UserDetails规范,可能引发认证异常。
修复步骤
1. 扩展API的路径匹配范围
修改API配置的securityMatcher,同时匹配/api和/api/**:
http.securityMatcher("/api", "/api/**")
2. 修复LDAP属性注入
修正属性注解的语法错误:
@Value("${ldap.search.ou}") private String ldapSearchOu;
3. 禁用API的请求缓存,避免重定向
在API的SecurityFilterChain中添加请求缓存禁用配置,防止认证失败后被重定向到登录页:
http.requestCache(request -> request.disable());
4. 修正UserDetails构造逻辑
确保User对象包含合法的密码和权限:
@Override public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { final UtilisateurDao utilisateurDao = utilisateurRepository.findByLogin(username); if (utilisateurDao == null) { throw new UsernameNotFoundException("用户不存在: " + username); } List<GrantedAuthority
相关产品推荐
相关产品推荐

