spring-bootkerberosspring-security-kerberos

how to configure spring boot kerberos implementation to get windows users id stored in ticket cache to achieve sso


im new to spring boot, im trying to integrate spring boot security with kerberos so that i can achieve sso and get windows user info in my application. since my windows user credential is stored in memory (i believe i dont need to setup krb5.conf and jaas.conf), all i've done so far is updated my pom.xml and securityconfiguration.java:

pom.xml:

<dependencies>
    <!-- Spring Boot Starter Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- Spring Security Kerberos -->
    <dependency>
        <groupId>org.springframework.security.kerberos</groupId>
        <artifactId>spring-security-kerberos-core</artifactId>
        <version>1.0.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.kerberos</groupId>
        <artifactId>spring-security-kerberos-web</artifactId>
        <version>1.0.1.RELEASE</version>
    </dependency>

    <!-- Thymeleaf for HTML templates -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

securityconfiguration.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider;
import org.springframework.security.kerberos.client.sun.SunJaasKerberosTicketValidator;
import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter;
import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorizeRequests ->
                authorizeRequests.anyRequest().authenticated()
            )
            .exceptionHandling(exceptionHandling ->
                exceptionHandling.authenticationEntryPoint(spnegoEntryPoint())
            )
            .addFilterBefore(spnegoAuthenticationProcessingFilter(), BasicAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public SpnegoEntryPoint spnegoEntryPoint() {
        return new SpnegoEntryPoint();
    }

    @Bean
    public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter() throws Exception {
        SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        List<org.springframework.security.authentication.AuthenticationProvider> providers = new ArrayList<>();
        providers.add(kerberosServiceAuthenticationProvider());
        return new ProviderManager(providers);
    }

    @Bean
    public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
        KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
        provider.setTicketValidator(sunJaasKerberosTicketValidator());
        provider.setUserDetailsService(userDetailsService());
        return provider;
    }

    @Bean
    public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
        SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
        // Do not set a service provider to leverage the Windows ticket cache
        // Do not set a keytab location to leverage the Windows ticket cache
        ticketValidator.setDebug(true);
        return ticketValidator;
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            return new User(username, "", authorities);
        };
    }
}

however, when i try to start my application, its still said that both service pricipal and key tab must be specified. the debug log is showing useTicketCache=false, but i clearly set it as true in my jaas.conf and added it to my run configuration in eclipse: jaas.conf:

com.sun.security.jgss.krb5.initiate {
    com.sun.security.auth.module.Krb5LoginModule required
    useTicketCache=true
    doNotPrompt=true
    renewTGT=true;
};

setting jaas.conf path:

String jaasConfigPath = "C:\\path\\to\\your\\jaas.conf";
System.setProperty("java.security.auth.login.config", jaasConfigPath);

please let me know if im missing anything, or how to tell the application that im trying to get user info from memory cache, im using windows 10, java 21, spring boot 3.3.2 and eclipse. thanks for the help


Solution

  • You have some things completely backwards.

    i dont have a key tab location, since when i use klist, i see 4 or 5 entries, none of them have key cache location specified, im assuming it is stored in memory

    Well yes, but they're in the client's memory, and your webapp's Java code is not running on the client system, thus it cannot see the client's memory or the client's ticket cache. So it makes no sense to find a server-side location from a client-side command.

    (This is still true even if the webapp happens to run on 'localhost' at the moment – the Java code and the browser still remain separate entities that only talk through HTTP, and the webapp cannot automatically see the client's cached tickets regardless. Of course, once it is deployed, it will be on a different machine altogether.)

    Second, what you see in klist is not a keytab. Don't confuse a ticket cache with a keytab – in a way they're completely opposite things; in general the ticket cache holds the user's login credentials, while the keytab holds the server's credentials. The keytab is kinda like a SSL certificate.

    (Of course, there are exceptions – sometimes services act as clients and have a ticket cache, and sometimes clients have keytabs, but hopefully that doesn't confuse the point too much.)

    however, when i try to start my application, its still said that both service pricipal and key tab must be specified. the debug log is showing useTicketCache=false, but i clearly set it as true in my jaas.conf and added it to my run configuration in eclipse: jaas.conf:

    useTicketCache is irrelevant, because a Kerberos service does not have a ticket cache (or a TGT for that matter) – only the client has those things.

    (This setting only exists for situations where the Java webapp itself was acting as a client to some other Kerberos-based system (e.g. talking to an API or LDAP). You might need it later on, but not for this particular task.)

    Again, do not confuse a ticket cache with a keytab. The server does not have a ticket cache, but it requires a keytab – you must get one issued, and you must specify one in the server's config. The browser running on the client would be the one accessing its ticket cache and sending a ticket over the network to your webapp, and the webapp would verify it against its keytab.