javaxmlspringspring-security-kerberoskeytab

Spring security kerberos works with xml config but not with Java config


I tried the examples of spring security kerberos in with xml config then with java config. It's exactly the same config (one in xml, one in java).

When I use the project with xml config, it works. However, when I use the project with java config, I have this stacktrace :

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:174)
    at org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter.doFilter(SpnegoAuthenticationProcessingFilter.java:145)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:121)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    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)
    ... 61 common frames omitted
Caused by: org.ietf.jgss.GSSException: Failure unspecified at GSS-API level (Mechanism level: Invalid argument (400) - Cannot find key of appropriate type to decrypt AP REP - AES256 CTS mode with HMAC SHA1-96)
    at sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:856)
    at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:342)
    at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285)
    at sun.security.jgss.spnego.SpNegoContext.GSS_acceptSecContext(SpNegoContext.java:906)
    at sun.security.jgss.spnego.SpNegoContext.acceptSecContext(SpNegoContext.java:556)
    at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:342)
    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:153)
    ... 64 common frames omitted
Caused by: sun.security.krb5.KrbException: Invalid argument (400) - Cannot find key of appropriate type to decrypt AP REP - AES256 CTS mode with HMAC SHA1-96
    at sun.security.krb5.KrbApReq.authenticate(KrbApReq.java:278)
    at sun.security.krb5.KrbApReq.<init>(KrbApReq.java:149)
    at sun.security.jgss.krb5.InitSecContextToken.<init>(InitSecContextToken.java:108)
    at sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:829)
    ... 72 common frames omitted

I don't understand why it works with xml config, and not with java config. I spend lots of time to search but I despair. Here my xml config :

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">

<sec:http entry-point-ref="spnegoEntryPoint" use-expressions="true" >
    <sec:intercept-url pattern="/" access="permitAll" />
    <sec:intercept-url pattern="/home" access="permitAll" />
    <sec:intercept-url pattern="/login" access="permitAll" />
    <sec:intercept-url pattern="/**" access="authenticated"/>
    <sec:form-login login-page="/login" />
    <sec:custom-filter ref="spnegoAuthenticationProcessingFilter"
        before="BASIC_AUTH_FILTER" />
</sec:http>

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="kerberosAuthenticationProvider" />
    <sec:authentication-provider ref="kerberosServiceAuthenticationProvider" />
</sec:authentication-manager>

<bean id="kerberosAuthenticationProvider"
    class="org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider">
    <property name="userDetailsService" ref="dummyUserDetailsService"/>
    <property name="kerberosClient">
        <bean class="org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient">
            <property name="debug" value="true"/>
        </bean>
    </property>
</bean>

<bean id="spnegoEntryPoint"
    class="org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint" >
    <constructor-arg value="/login" />
</bean>

<bean id="spnegoAuthenticationProcessingFilter"
    class="org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager" />
</bean>

<bean id="kerberosServiceAuthenticationProvider"
    class="org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider">
    <property name="ticketValidator">
        <bean
            class="org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator">
            <property name="servicePrincipal" value="${app.service-principal}" />
            <property name="keyTabLocation" value="${app.keytab-location}" />
            <property name="debug" value="true" />
        </bean>
    </property>
    <property name="userDetailsService" ref="dummyUserDetailsService" />
</bean>

<bean id="dummyUserDetailsService" class="demo.DummyUserDetailsService" />

</beans>

Here my java config :

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Value("${app.service-principal}")
private String servicePrincipal;

@Value("${app.keytab-location}")
private String keytabLocation;

@Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
    return authenticationManager();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .exceptionHandling()
            .authenticationEntryPoint(spnegoEntryPoint())
            .and()
        .authorizeRequests()
            .antMatchers("/", "/home").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(kerberosAuthenticationProvider())
        .authenticationProvider(kerberosServiceAuthenticationProvider());
}

@Bean
public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
    KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider();
    SunJaasKerberosClient client = new SunJaasKerberosClient();
    client.setDebug(true);
    provider.setKerberosClient(client);
    provider.setUserDetailsService(dummyUserDetailsService());
    return provider;
}

@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(dummyUserDetailsService());
    return provider;
}

@Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
    SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
    ticketValidator.setServicePrincipal(servicePrincipal);
    ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
    ticketValidator.setDebug(true);
    //ticketValidator.setHoldOnToGSSContext(true);
    return ticketValidator;
}

@Bean
public DummyUserDetailsService dummyUserDetailsService() {
    return new DummyUserDetailsService();
}

}

I specify that I use the same keytab for both projects, compile on the same environment with the same spring boot config.

With xml config, the logs show

2017-08-25 12:26:37.315 DEBUG 2297 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager     : Authentication attempt using org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider
2017-08-25 12:26:37.315 DEBUG 2297 --- [nio-9000-exec-2] .a.KerberosServiceAuthenticationProvider : Try to validate Kerberos Token
Found KeyTab /tmp/krb5.keytab for HTTP/mcottech2.example.com@EXAMPLE.COM
Found KeyTab /tmp/krb5.keytab for HTTP/mcottech2.example.com@EXAMPLE.COM
Entered Krb5Context.acceptSecContext with state=STATE_NEW
Java config name: file:///etc/krb5.conf
KeyTabInputStream, readName(): EXAMPLE.COM
KeyTabInputStream, readName(): HTTP
KeyTabInputStream, readName(): mcottech2.example.com
KeyTab: load() entry length: 65; type: 1
KeyTabInputStream, readName(): EXAMPLE.COM
KeyTabInputStream, readName(): HTTP
KeyTab: load() entry length: 61; type: 23
Looking for keys for: HTTP/mcottech2.example.com@EXAMPLE.COM
Added key: 23version: 45
Added key: 18version: 45
Added key: 17version: 45
Found unsupported keytype (3) for HTTP/mcottech2.example.com@EXAMPLE.COM
Found unsupported keytype (1) for HTTP/mcottech2.example.com@EXAMPLE.COM
Added key: 17version: 44
Added key: 23version: 44
Added key: 18version: 44
Added key: 17version: 44
Found unsupported keytype (3) for HTTP/mcottech2.example.com@EXAMPLE.COM
Found unsupported keytype (1) for HTTP/mcottech2.example.com@EXAMPLE.COM
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
Using builtin default etypes for permitted_enctypes
default etypes for permitted_enctypes: 18 17 16 23.
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
MemoryCache: add 1503663997/005947/0A6D3E392B245A589F7F3FF28BA5991F/toto.toto@EXAMPLE.COM to toto.toto@EXAMPLE.COM|HTTP/mcottech2.example.com@EXAMPLE.COM
>>> KrbApReq: authenticate succeed.
Krb5Context setting peerSeqNumber to: 1289436574
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
Krb5Context setting mySeqNumber to: 502989002
>>> Constrained deleg from GSSCaller{UNKNOWN}
2017-08-25 12:26:37.364 DEBUG 2297 --- [nio-9000-exec-2] .a.KerberosServiceAuthenticationProvider : Succesfully validated toto.toto@EXAMPLE.COM

whereas with Java config example I've only got

2017-08-25 12:21:11.778 DEBUG 2248 --- [nio-9000-exec-4] .a.KerberosServiceAuthenticationProvider : Try to validate Kerberos Token
Found KeyTab /tmp/file:/tmp/krb5.keytab for HTTP/mcottech2.example.com@EXAMPLE.COM
Found KeyTab /tmp/file:/tmp/krb5.keytab for HTTP/mcottech2.example.com@EXAMPLE.COM
Entered Krb5Context.acceptSecContext with state=STATE_NEW
Java config name: file:///etc/krb5.conf
Looking for keys for: HTTP/mcottech2.example.com@EXAMPLE.COM
Looking for keys for: HTTP/mcottech2.example.com@EXAMPLE.COM

Is there something I forgot?


Solution

  • With xml config, in application.yml, the keytab location must be an absolute path precede by "file://"

    keytab-location: file:///tmp/krb5.keytab
    

    With java config, in application.yml, the keytab location must be an relative path wanting file protocol.

    keytab-location: krb5.keytab