1

Intro

I am using Spring Securities Kerberos authentication to handle logging into by website. I followed the instructions here and used the code from here to authenticate the user. In Firefox, everything is successful, the login page below pops up as expected and I can login in using my windows login. enter image description here

However, the authentication fails in IE and Chrome. Instead of the login screen showing up, a popup asking for the password is shown. When I put in the Windows user and password I get the screen below. enter image description here No error shows up server side from Spring despite the HTTP Error 500 being displayed on Chrome and IE.

Research

I tried to run the sample code provided here (under spring-security-kerberos-samples/sec-server-win-auth), however the same issue persists. In this case however, Spring returns the following error

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

I made sure the user and password was correct, but still authentication failed on IE and Chrome, but was successful in Firefox.

Additionally, I tried following the tutorial here which is suppose to allow Kerberos authentication on IE. The only different is the password is not prompted, but a 500 error is shown on the page.

Question

Is there a way for me to change the configuration of the project to work across all browsers, or is by current method of authentication only going to work with Firefox?

Associated Files

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}))"

If there is anything I forgot please tell me, and thank you for your time.

UPDATE

Chrome will work as long as I exit the popup window.

cbolles
  • 475
  • 5
  • 17
  • 1
    [here](https://ping.force.com/Support/PingFederate/Integrations/How-to-configure-supported-browsers-for-Kerberos-NTLM) is an article addressing this issue on multiple browsers, [here](https://productforums.google.com/forum/?hl=en#!category-topic/chrome/report-a-problem-and-get-troubleshooting-help/lIw5ojBDER4) the according "Chorme Help Forum" enrtry. – xerx593 Jul 11 '17 at 15:52
  • Sorry @xerx593 should have mentioned in research section. I tried the tutorial in the first link with the same result (only difference is the password is not prompted) with the displayed 500 error. Ill make the edit now. – cbolles Jul 11 '17 at 15:58

3 Answers3

1

I managed to fix the problem with a little bit of messing around. It was in

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);

The exceptionHandling() caused a popup to show up that asked for the user and password when the user attempted to connect to a page that they were not authenticated to view. The popup did not seem to be configured properly to authenticate the user. I deleted this bit of code

.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()

since I already had a custom login page the user gets redirected to anyway. No more errors and users can login in on IE, Chrome, and Firefox.

cbolles
  • 475
  • 5
  • 17
  • I am also facing same problem and I suspect that, by disabling the above code, it is falling back on NTLM authentication rather than using Kerberos. Can you help me out here, if you were able to actually made the Kerberos working? – Milan Pandey Jan 21 '19 at 13:01
1

For anyone dealing with this - yes, if you omit the spnegoEntryPoint, as skagra_dragneel suggested, it will work fine. But if you want single sign on on machines in Windows domain, it is not an option, because you need the entry point to send the negotiate header.

So far, as I have been playing around with this SSO / Kerberos / Spring security thing for last few days, I have seen this behaviour:

  • on SSO scenario, everything works both in Chrome and Firefox
  • on machines outside the Windows domain, the Firefox works fine. It displays the login form. But Chrome doesn't work - it displays the browsers login window. If you choose Storno, it works fine, it accepts redirect to login page. But if you fill credentials and hit OK, i will send some nonsense to server as kerberos ticket. Validation of this ticket will fail and SpnegoAuthenticationProcessingFilter will therefore response with 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;
}

So far the only solution, I have found, is to register error handler to Spnego filter and in case of wrong kerberos ticket, do the redirection to login page:

@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;
}

This will work in both scenarios - SSO and also outside Windows domain.

lachty
  • 220
  • 2
  • 6
0

It looks like configuration problem (with SPN... not Java code). I suppose that Kerberos doesn't work at all, but Firefox uses NTLM and this is why it works. Chrome usually use configuration from IE. And IE may be more strict then Firefox and doesn't allow authentication with bad ticket:

GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)

You can look at requests using Fiddler - here is some instruction.

Double check your service account and SPN:

setspn.exe -L accountname

In case of HTTP protocol, SPN should have a form of HTTP/machineName.your.domain.com

Make sure that you use address http://machineName.your.domain.com to access your application. Of course you can add specific port (e.g 8080) or specific path after URL. But use the same URL as used in SPN - don't use IP address.

I also suggest to selectively follow this guide: SPNEGO SSO using Kerberos.

Here is also some old thread about SPN construction from Microsoft.

Find if there is any SPN duplicate in other account:

setspn.exe -Q <SPN>

SetSPN command sytax.

SPN duplication can cause NTLM fallback as said in this question.

And finally search more about Defective token problem - here are some good answers.

jsosnowski
  • 1,560
  • 3
  • 26
  • 56