Spring Security Kerberos与MiniKdc的集成

2023/05/17

1. 概述

在本教程中,我们将概述Spring Security Kerberos。

我们将用Java编写一个Kerberos客户端,该客户端授权自己访问我们的Kerberos服务。我们将运行我们自己的嵌入式密钥分发中心来执行完整的端到端Kerberos身份验证。由于Spring Security Kerberos,所有这些都不需要任何外部基础设施。

2. Kerberos及其好处

Kerberos是麻省理工学院在1980年代创建的一种网络身份验证协议,特别适用于在网络上集中进行身份验证。

1987年,麻省理工学院将其发布到开源社区,目前仍在积极开发中。2005年,它被推崇为RFC 4120下的IETF标准。

通常,Kerberos用于企业环境。在那里,它以用户不必分别对每个服务进行身份验证的方式保护环境。这种架构解决方案称为单点登录

简单地说,Kerberos是一个票务系统。用户验证一次并收到票证授予票证(TGT)。然后,网络基础设施将该TGT交换为服务票证。这些服务票证允许用户与基础设施服务交互(只要TGT有效,通常持续几个小时)。

因此,用户只需登录一次就很好了。但也有安全方面的好处:在这样的环境中,用户的密码永远不会通过网络发送。相反,Kerberos使用它作为一个因素来生成另一个密钥,该密钥将用于消息加密和解密。

另一个好处是我们可以从一个中心位置管理用户,比如说一个由LDAP支持的地方。因此,如果我们为给定用户禁用集中式数据库中的帐户,那么我们将撤销他在我们基础设施中的访问权限。因此,管理员不必在每个服务中单独撤销访问权限。

Spring中的SPNEGO/Kerberos身份验证简介提供了对该技术的深入概述。

3. Kerberos环境

因此,让我们创建一个使用Kerberos协议进行身份验证的环境。该环境将由三个同时运行的独立应用程序组成。

首先,我们将有一个密钥分发中心作为身份验证点。接下来,我们将编写一个客户端和一个我们将配置为使用Kerberos协议的服务应用程序。

现在,运行Kerberos需要一些安装和配置。但是,我们将利用Spring Security Kerberos,因此我们将在嵌入式模式下以编程方式运行密钥分发中心。此外,下面显示的MiniKdc在与Kerberized基础设施进行集成测试时很有用。

3.1 运行密钥分发中心

首先,我们将启动我们的密钥分发中心,它将为我们发布TGT:

String[] config = MiniKdcConfigBuilder.builder()
    .workDir(prepareWorkDir())
    .principals("client/localhost", "HTTP/localhost")
    .confDir("minikdc-krb5.conf")
    .keytabName("example.keytab")
    .build();

MiniKdc.main(config);

基本上,我们已经为MiniKdc提供了一组主体和一个配置文件;此外,我们已经告诉MiniKdc如何调用它生成的keytab

MiniKdc将生成一个krb5.conf文件,我们将其提供给我们的客户端和服务应用程序。该文件包含在哪里可以找到我们的KDC的信息-给定域的主机和端口。

MiniKdc.main启动KDC并应输出如下内容:

Standalone MiniKdc Running
---------------------------------------------------
  Realm           : EXAMPLE.COM
  Running at      : localhost:localhost
  krb5conf        : .\spring-security-sso\spring-security-sso-kerberos\krb-test-workdir\krb5.conf

  created keytab  : .\spring-security-sso\spring-security-sso-kerberos\krb-test-workdir\example.keytab
  with principals : [client/localhost, HTTP/localhost]

3.2 客户端应用程序

我们的客户端将是一个Spring Boot应用程序,它使用RestTemplate来调用外部REST API。

但是,我们将改用KerberosRestTemplate。它需要keytab和客户端的主体:

@Configuration
public class KerberosConfig {

    @Value("${app.user-principal:client/localhost}")
    private String principal;

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

    @Bean
    public RestTemplate restTemplate() {
        return new KerberosRestTemplate(keytabLocation, principal);
    }
}

就是这样!KerberosRestTemplate为我们协商Kerberos协议的客户端。

因此,让我们创建一个快速类,它将从托管在端点app.access-url的Kerberized服务中查询一些数据:

@Service
class SampleClient {

    @Value("${app.access-url}")
    private String endpoint;

    private RestTemplate restTemplate;

    // constructor, getter, setter

    String getData() {
        return restTemplate.getForObject(endpoint, String.class);
    }
}

那么,让我们现在创建我们的服务应用程序,这样这个类就有东西可以调用了!

3.3 服务端应用程序

我们将使用Spring Security,并使用适当的特定于Kerberos的bean对其进行配置。

另外,请注意该服务也将有其主体并使用keytab:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends AbstractHttpConfigurer<WebSecurityConfig, HttpSecurity> {

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

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

    public static WebSecurityConfig securityConfig() {
        return new WebSecurityConfig();
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        AuthenticationManager authenticationManager =
              http.getSharedObject(AuthenticationManager.class);
        http.addFilterBefore(spnegoAuthenticationProcessingFilter(authenticationManager),
              BasicAuthenticationFilter.class);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.exceptionHandling()
              .authenticationEntryPoint(spnegoEntryPoint())
              .and()
              .authorizeRequests()
              .antMatchers("/", "/home")
              .permitAll()
              .anyRequest()
              .authenticated()
              .and()
              .formLogin()
              .loginPage("/login")
              .permitAll()
              .and()
              .logout()
              .permitAll()
              .and()
              .apply(securityConfig());
        return http.build();
    }

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
              .authenticationProvider(kerberosAuthenticationProvider())
              .authenticationProvider(kerberosServiceAuthenticationProvider())
              .build();
    }

    @Bean
    public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
        KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider();
        // provider configuration
        return provider;
    }

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

    @Bean
    public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(
          AuthenticationManager authenticationManager) {
        SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
        // filter configuration
        return filter;
    }

    @Bean
    public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
        KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
        // auth provider configuration 
        return provider;
    }

    @Bean
    public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
        SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
        // validator configuration
        return ticketValidator;
    }
}

介绍文章包含上面的所有实现,因此为了简洁起见,我们在这里省略了完整的方法。

请注意,我们已经为SPNEGO身份验证配置了Spring Security。这样,我们将能够通过HTTP协议进行身份验证,尽管我们也可以使用核心Java实现SPNEGO身份验证

4. 测试

现在,我们将运行一个集成测试,以显示我们的客户端成功地通过Kerberos协议从外部服务器检索数据。要运行此测试,我们需要运行基础结构,因此必须启动MiniKdc和我们的服务应用程序。

基本上,我们将使用客户端应用程序中的SampleClient向我们的服务应用程序发出请求。让我们测试一下:

@Autowired
private SampleClient sampleClient;

@Test
public void givenKerberizedRestTemplate_whenServiceCall_thenSuccess() {
    assertEquals("data from kerberized server", sampleClient.getData());
}

请注意,我们还可以通过在没有KerberizedRestTemplate的情况下访问服务来证明KerberizedRestTemplate很重要:

@Test
public void givenRestTemplate_whenServiceCall_thenFail() {
    sampleClient.setRestTemplate(new RestTemplate());
    assertThrows(RestClientException.class, sampleClient::getData);
}

作为旁注,我们的第二个测试有可能重新使用已存储在凭证缓存中的票证。这是由于HttpUrlConnection中使用的自动SPNEGO协商而发生的。

结果,数据可能实际上返回,使我们的测试无效。然后,根据我们的需要,我们可以通过系统属性http.use.global.creds=false禁用票证缓存使用。

5. 总结

在本教程中,我们探讨了用于集中式用户管理的Kerberos,以及Spring Security如何支持Kerberos协议和SPNEGO身份验证机制。

我们使用MiniKdc建立嵌入式KDC,还创建了一个非常简单的Kerberized客户端和服务器。这个设置对于探索来说很方便,尤其是当我们创建一个集成测试来测试的时候。

现在,我们只是触及了表面。要深入了解,请查看Kerberos wiki页面或其RFC。此外,官方文档页面也会很有用。除此之外,要了解如何在核心Java中完成这些事情,以下Oracle的教程将详细说明。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章