【问题标题】:Kerberos Authentication through Spring Security Failing in IE11 and Chrome but not Firefox通过 Spring Security 的 Kerberos 身份验证在 IE11 和 Chrome 但不是 Firefox 中失败
【发布时间】:2017-12-15 18:51:45
【问题描述】:

简介

我正在使用 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 就会工作。

【问题讨论】:

  • here 是一篇在多个浏览器上解决此问题的文章,here 根据“Chorme 帮助论坛”条目。
  • 对不起@xerx593 应该在研究部分提到。我尝试了第一个链接中的教程,结果相同(唯一的区别是不提示密码),显示 500 错误。我现在进行编辑。

标签: java spring google-chrome spring-security kerberos


【解决方案1】:

看起来像配置问题(使用 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 - 不要使用 IP 地址。

我还建议选择性地遵循本指南:SPNEGO SSO using Kerberos

Here 也是微软关于 SPN 构建的一些旧线程。

查找其他帐户中是否有任何 SPN 重复项:

setspn.exe -Q <SPN>

SetSPN command sytax.

SPN 重复可能导致 NTLM 回退,如 this question 所述。

最后搜索有关缺陷令牌问题的更多信息 - here are some good answers

【讨论】:

    【解决方案2】:

    我设法解决了一些问题。它在

    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 上登录。

    【讨论】:

    • 我也面临同样的问题,我怀疑通过禁用上述代码,它会退回到 NTLM 身份验证而不是使用 Kerberos。如果你能够真正让 Kerberos 工作,你能帮我吗?
    【解决方案3】:

    对于任何处理这个问题的人 - 是的,如果你省略 spnegoEntryPoint,就像 skagra_dragneel 建议的那样,它会正常工作。但是,如果您想在 Windows 域中的计算机上进行单点登录,则不是一种选择,因为您需要入口点来发送协商标头。

    到目前为止,由于过去几天我一直在玩这个 SSO / Kerberos / Spring 安全问题,我看到了这种行为:

    • 在 SSO 场景中,一切都在 Chrome 和 Firefox 中运行
    • 在 Windows 域以外的机器上,Firefox 可以正常工作。它显示登录表单。但 Chrome 不起作用 - 它显示浏览器登录窗口。如果您选择 Storno,它可以正常工作,它接受重定向到登录页面。但是,如果您填写凭据并单击“确定”,我会将一些废话作为 kerberos 票证发送到服务器。此票证的验证将失败,因此 SpnegoAuthenticationProcessingFilter 将响应 500:
    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 域之外。

    【讨论】:

      猜你喜欢
      • 2016-12-28
      • 2015-07-28
      • 1970-01-01
      • 2015-12-11
      • 2022-08-16
      • 2016-03-24
      • 2012-08-15
      • 1970-01-01
      • 2013-11-13
      相关资源
      最近更新 更多