简介
我正在使用Spring Securities Kerberos身份验证来处理网站登录。我按照here的说明操作,并使用here中的代码对用户进行身份验证。在Firefox中,一切都很成功,下面的登录页面按预期弹出,我可以使用我的Windows登录登录。
但是,IE和Chrome中的身份验证失败。不显示登录屏幕,而是显示要求输入密码的弹出窗口。当我输入Windows用户和密码时,我会看到下面的屏幕。 尽管在Chrome和IE上显示HTTP错误500,但没有错误显示Spring的服务器端。
研究
我尝试运行提供的示例代码here(在spring-security-kerberos-samples / sec-server-win-auth下),但同样的问题仍然存在。但是,在这种情况下,Spring返回以下错误
org.springframework.security.authentication.BadCredentialsException: Kerberos validation not successful
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator.validateTicket(SunJaasKerberosTicketValidator.java:71)
at org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider.authenticate(KerberosServiceAuthenticationProvider.java:64)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:177)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:436)
at org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter.doFilter(SpnegoAuthenticationProcessingFilter.java:145)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:85)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:537)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1556)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1513)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.security.PrivilegedActionException: null
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator.validateTicket(SunJaasKerberosTicketValidator.java:68)
... 45 common frames omitted
Caused by: org.ietf.jgss.GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
at sun.security.jgss.GSSHeader.<init>(GSSHeader.java:97)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:306)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285)
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator$KerberosValidateAction.run(SunJaasKerberosTicketValidator.java:170)
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator$KerberosValidateAction.run(SunJaasKerberosTicketValidator.java:1)
... 48 common frames omitted
我确保用户和密码是正确的,但在IE和Chrome上仍然验证失败,但在Firefox中成功。
此外,我尝试按照教程here进行操作,该教程假设允许在IE上进行Kerberos身份验证。唯一不同的是未提示密码,但页面上显示500错误。
问题
我是否可以通过所有浏览器更改项目配置,或者通过当前的身份验证方法只能与Firefox一起使用?
相关文件
的build.gradle
buildscript {
ext {
springBootVersion = '1.4.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
jar {
baseName = 'vlgx-portal-app'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.7
targetCompatibility = 1.7
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-jersey')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-web-services')
compile('org.jsoup:jsoup:1.8.1')
compile ('javax.mail:mail:1.4.7')
compile 'org.springframework.security.kerberos:spring-security-kerberos-web:1.0.1.RELEASE'
compile 'org.springframework.security.kerberos:spring-security-kerberos-client:1.0.1.RELEASE'
compile 'org.springframework.security:spring-security-ldap:4.2.3.RELEASE'
//runtime('org.postgresql:postgresql')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
WebSecurityConfig.java
package com.valogix.portal.configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider;
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator;
import org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig;
import org.springframework.security.kerberos.client.ldap.KerberosLdapContextSource;
import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter;
import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.ldap.userdetails.LdapUserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${app.ad-domain}")
private String adDomain;
@Value("${app.ad-server}")
private String adServer;
@Value("${app.service-principal}")
private String servicePrincipal;
@Value("${app.keytab-location}")
private String keytabLocation;
@Value("${app.ldap-search-base}")
private String ldapSearchBase;
@Value("${app.ldap-search-filter}")
private String ldapSearchFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll()
.and()
.addFilterBefore(
spnegoAuthenticationProcessingFilter(authenticationManagerBean()),
BasicAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider());
}
@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
return new ActiveDirectoryLdapAuthenticationProvider(adDomain, adServer);
}
@Bean
public SpnegoEntryPoint spnegoEntryPoint() {
return new SpnegoEntryPoint("/login");
}
@Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(
AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
@Bean
public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(ldapUserDetailsService());
return provider;
}
@Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(servicePrincipal);
ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
ticketValidator.setDebug(true);
return ticketValidator;
}
@Bean
public KerberosLdapContextSource kerberosLdapContextSource() {
KerberosLdapContextSource contextSource = new KerberosLdapContextSource(adServer);
contextSource.setLoginConfig(loginConfig());
return contextSource;
}
@Bean
public SunJaasKrb5LoginConfig loginConfig() {
SunJaasKrb5LoginConfig loginConfig = new SunJaasKrb5LoginConfig();
loginConfig.setKeyTabLocation(new FileSystemResource(keytabLocation));
loginConfig.setServicePrincipal(servicePrincipal);
loginConfig.setDebug(true);
loginConfig.setIsInitiator(true);
return loginConfig;
}
@Bean
public LdapUserDetailsService ldapUserDetailsService() {
FilterBasedLdapUserSearch userSearch =
new FilterBasedLdapUserSearch(ldapSearchBase, ldapSearchFilter, kerberosLdapContextSource());
LdapUserDetailsService service = new LdapUserDetailsService(userSearch);
service.setUserDetailsMapper(new LdapUserDetailsMapper());
return service;
}
}
application.properties
server.port = 8096
customerServiceEmail = "example@gmail.com"
errorLogDirectory = "error_log_path"
app.ad-domain: Domain
app.ad-server: ad_server
app.service-principal: HTTP/path_or_something
app.keytab-location: /tmp/tomcat.keytab
app.ldap-search-base: dc=example,dc=org
app.ldap-search-filter: "(| (userPrincipalName={0}) (sAMAccountName={0}))"
如果有什么我忘了请告诉我,谢谢你的时间。
更新
只要我退出弹出窗口,Chrome就会正常工作。答案 0 :(得分:1)
我设法解决了一些问题。它在
http
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll()
.and()
.addFilterBefore(
spnegoAuthenticationProcessingFilter(authenticationManagerBean()),
BasicAuthenticationFilter.class);
当用户尝试连接到未经过身份验证的页面时,exceptionHandling()会导致显示要求用户和密码的弹出窗口。弹出窗口似乎没有正确配置以验证用户。我删除了这段代码
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
因为我已经有了一个自定义登录页面,所以用户无论如何都会被重定向。没有更多错误,用户可以登录IE,Chrome和Firefox。
答案 1 :(得分:0)
它看起来像配置问题(使用SPN ...而不是Java代码)。我认为Kerberos根本不起作用,但Firefox使用NTLM,这就是它工作的原因。 Chrome通常使用IE中的配置。 IE可能比Firefox更严格,并且不允许使用坏票进行身份验证:
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
您可以使用Fiddler查看请求 - 此处为some instruction。
仔细检查您的服务帐户和SPN:
setspn.exe -L accountname
如果是HTTP协议,SPN的格式应为HTTP/machineName.your.domain.com
确保使用地址http://machineName.your.domain.com
访问您的应用程序。当然,您可以在URL后添加特定端口(例如8080
)或特定路径。但是使用与SPN中使用的URL相同的URL - 不要使用IP地址。
我还建议您有选择地遵循本指南:SPNEGO SSO using Kerberos。
Here也是关于微软SPN构建的一些老话题。
查看其他帐户中是否有任何SPN重复:
setspn.exe -Q <SPN>
SPN重复可能导致NTLM回退,如this question中所述。
最后搜索有关瑕疵令牌问题的更多信息 - here are some good answers。
答案 2 :(得分:0)
对于处理此问题的任何人-是的,如果您忽略skagra_dragneel所建议的spnegoEntryPoint,它将正常工作。但是,如果您要在Windows域中的计算机上进行单点登录,则不是一个选择,因为您需要入口点来发送协商标头。
到目前为止,由于最近几天我一直在使用这种SSO / Kerberos / Spring安全性,我已经看到了这种行为:
try {
authentication = authenticationManager.authenticate(authenticationRequest);
} catch (AuthenticationException e) {
// That shouldn't happen, as it is most likely a wrong
// configuration on the server side
logger.warn("Negotiate Header was invalid: " + header, e);
SecurityContextHolder.clearContext();
if (failureHandler != null) {
failureHandler.onAuthenticationFailure(request, response, e);
} else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.flushBuffer();
}
return;
}
到目前为止,我发现的唯一解决方案是将错误处理程序注册到Spnego过滤器,并在错误的kerberos票证的情况下,重定向到登录页面:
@Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter( AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//redirection in case of wrong ticket
response.sendRedirect("login");
}
});
return filter;
}
这将在两种情况下均适用-SSO以及Windows域外部。