Spring Security Kerberos中POST请求Spnego认证失败问题排查
问题:Spring Security Kerberos POST请求认证失败返回登录页
背景
基于Spring Security Kerberos的sec-server-win-auth示例扩展开发,新增了包含GET/POST映射的RestController,使用Swagger测试接口。搭建Active Directory服务器后,访问Swagger端点会弹出Windows登录窗口,认证成功可进入Swagger UI,但执行POST请求时,响应体返回带“Invalid username and password.”提示的登录页HTML,GET请求则正常工作。
错误响应示例
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Kerberos Example</title> <link rel="icon" href="data:,"> </head> <body> <div> Invalid username and password. </div> <form action="/login" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><input type="submit" value="Sign In"/></div> </form> </body> </html>
关键日志
2024-05-08 11:30:13,508 [DEBUG|org.springframework.security.web.FilterChainProxy|FilterChainProxy] Securing POST /config 2024-05-08 11:30:13,509 [DEBUG|org.springframework.security.web.authentication.AnonymousAuthenticationFilter|AnonymousAuthenticationFilter] Set SecurityContextHolder to anonymous SecurityContext 2024-05-08 11:30:13,509 [DEBUG|org.springframework.security.web.savedrequest.HttpSessionRequestCache|HttpSessionRequestCache] Saved request https://myserver.test.local/config?continue to session 2024-05-08 11:30:13,510 [DEBUG|org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint|SpnegoEntryPoint] Add header WWW-Authenticate:Negotiate to https://myserver.test.local/config, forward: /login 2024-05-08 11:30:13,515 [DEBUG|org.springframework.security.web.FilterChainProxy|FilterChainProxy] Securing POST /login 2024-05-08 11:30:13,517 [DEBUG|org.springframework.security.web.DefaultRedirectStrategy|DefaultRedirectStrategy] Redirecting to /login?error 2024-05-08 11:30:13,525 [DEBUG|org.springframework.security.web.FilterChainProxy|FilterChainProxy] Securing GET /login?error 2024-05-08 11:30:13,527 [DEBUG|org.springframework.security.web.FilterChainProxy|FilterChainProxy] Secured GET /login?error 2024-05-08 11:30:13,528 [DEBUG|org.springframework.web.servlet.DispatcherServlet|LogFormatUtils] GET "/login?error", parameters={masked} 2024-05-08 11:30:13,528 [DEBUG|org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping|AbstractHandlerMapping] Mapped to org.example.MainController#login() 2024-05-08 11:30:13,532 [DEBUG|org.springframework.web.servlet.DispatcherServlet|FrameworkServlet] Completed 200 OK 2024-05-08 11:30:13,532 [DEBUG|org.springframework.security.web.authentication.AnonymousAuthenticationFilter|AnonymousAuthenticationFilter] Set SecurityContextHolder to anonymous SecurityContext
调试发现AbstractLdapAuthenticationProvider::authenticate方法被调用并抛出BadCredentialsException,其中username和password变量为空字符串。推测是Spnego认证过程中发起了POST /login请求,与登录页点击登录按钮的行为一致,且请求方法始终与初始请求相同(测试DELETE请求也出现同样问题)。
疑问
- 为何
AbstractLdapAuthenticationProvider会被调用?能否禁用它,仅使用KerberosServiceAuthenticationProvider? - 为何Spnego认证过程中会出现forward?(日志:Add header WWW-Authenticate:Negotiate to https://myserver.test.local/config, forward: /login)
- 为何请求HTTP方法始终与初始请求相同?
配置代码
/* imports omitted */ @Configuration @EnableWebSecurity public class WebSecurityConfig { @Autowired private SpringConfig config; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider = kerberosServiceAuthenticationProvider(); ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = activeDirectoryLdapAuthenticationProvider(); ProviderManager providerManager = new ProviderManager(kerberosServiceAuthenticationProvider, activeDirectoryLdapAuthenticationProvider); http .authorizeHttpRequests(authz -> authz .anyRequest() .authenticated() ) .exceptionHandling(exceptionHandling -> exceptionHandling .authenticationEntryPoint(spnegoEntryPoint()) ) .formLogin(formLogin -> formLogin .loginPage(config.getActiveDirectoryLoginSerlvet()) .permitAll() ) .logout(logout -> logout .permitAll() ) .authenticationProvider(activeDirectoryLdapAuthenticationProvider) .authenticationProvider(kerberosServiceAuthenticationProvider) .addFilterBefore(spnegoAuthenticationProcessingFilter(providerManager), BasicAuthenticationFilter.class) .csrf(csrf -> csrf .disable() ); return http.build(); } @Bean public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() { return new ActiveDirectoryLdapAuthenticationProvider(config.getActiveDirectoryDomain(), config.getActiveDirectoryServer()); } @Bean public SpnegoEntryPoint spnegoEntryPoint() { return new SpnegoEntryPoint(config.getActiveDirectoryLoginSerlvet()); } // @Bean public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(AuthenticationManager authenticationManager) { SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter(); filter.setAuthenticationManager(authenticationManager); return filter; } public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() throws Exception { KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider(); provider.setTicketValidator(sunJaasKerberosTicketValidator()); provider.setUserDetailsService(ldapUserDetailsService()); return provider; } @Bean public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() { SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator(); ticketValidator.setServicePrincipal(config.getActiveDirectoryServicePrincipal()); ticketValidator.setKeyTabLocation(new FileSystemResource(config.getActiveDirectoryKeytabLocation())); ticketValidator.setDebug(true); return ticketValidator; } @Bean public KerberosLdapContextSource kerberosLdapContextSource() throws Exception { KerberosLdapContextSource contextSource = new KerberosLdapContextSource(config.getActiveDirectoryServer()); contextSource.setLoginConfig(loginConfig()); return contextSource; } public SunJaasKrb5LoginConfig loginConfig() throws Exception { SunJaasKrb5LoginConfig loginConfig = new SunJaasKrb5LoginConfig(); loginConfig.setKeyTabLocation(new FileSystemResource(config.getActiveDirectoryKeytabLocation())); loginConfig.setServicePrincipal(config.getActiveDirectoryServicePrincipal()); loginConfig.setDebug(true); loginConfig.setIsInitiator(true); loginConfig.afterPropertiesSet(); return loginConfig; } @Bean public LdapUserDetailsService ldapUserDetailsService() throws Exception { FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(config.getActiveDirectoryLdapSearchBase(), config.getActiveDirectoryLdapSearchFilter(), kerberosLdapContextSource()); LdapUserDetailsService service = new LdapUserDetailsService(userSearch, new ActiveDirectoryLdapAuthoritiesPopulator()); service.setUserDetailsMapper(new LdapUserDetailsMapper()); return service; } }
内容的提问来源于stack exchange,提问作者Benjamin
相关产品推荐
相关产品推荐

