Spring 安全 kerberos 适用于 xml 配置但不适用于 Java 配置

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

我在 xml config then with java config 中尝试了 spring 安全 kerberos 的示例。它是完全相同的配置(xml 中的一个,java 中的一个)。

当我使用带有 xml 配置的项目时,它可以工作。 但是,当我使用带有 java 配置的项目时,我有这个堆栈跟踪:

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

我不明白为什么它适用于 xml 配置,而不适用于 java 配置。我花了很多时间去寻找,但我很绝望。 这是我的 xml 配置:

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

这是我的 java 配置:

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

}

我指定我对两个项目使用相同的密钥表,在相同的环境中使用相同的 spring 启动配置进行编译。

使用 xml 配置,日志显示

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

而对于 Java 配置示例,我只有

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

有什么我忘了的吗?

使用 xml 配置,在 application.yml 中,keytab 位置必须是绝对路径,前面有 "file://"

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

使用 java 配置,在 application.yml 中,keytab 位置必须是需要文件协议的相对路径。

keytab-location: krb5.keytab