1. 概述

在本教程中,我们将介绍 Spring Security Kerberos 模块的基本原理与使用方法。

我们会编写一个 Java 的 Kerberos 客户端,通过 Kerberos 协议访问我们构建的服务端。同时,我们还会运行一个嵌入式的 KDC(Key Distribution Center,密钥分发中心),实现端到端的 Kerberos 身份认证流程。整个过程无需依赖外部基础设施,完全通过 Spring Security Kerberos 实现。

2. Kerberos 及其优势

Kerberos 是 MIT 在上世纪 80 年代开发的一种网络认证协议,适用于集中式认证的场景。

它在 1987 年开源(项目地址 krb5),至今仍在积极维护。2005 年,Kerberos 成为 IETF 标准协议(RFC 4120)。

主要优势如下:

集中认证:适用于企业内部环境,用户只需登录一次即可访问多个服务(即 SSO:Single Sign-on)
安全性高:用户密码不会在网络上传输,而是用于生成用于加密通信的密钥
统一权限管理:通常与 LDAP 集成,便于统一管理用户权限,一旦账户被禁用,所有服务的访问权限也会被同步撤销

简单来说,Kerberos 是一个“票据系统”:

  • 用户登录后获得 TGT(Ticket-Granting Ticket)
  • 后续访问服务时,使用 TGT 向 KDC 换取服务票据(Service Ticket)
  • 只要 TGT 有效(通常几小时),用户就可以无感知地访问服务

如需更深入了解 Kerberos 和 SPNEGO 的工作原理,可以参考:Spring 中的 SPNEGO/Kerberos 认证介绍

3. 构建 Kerberos 环境

我们将构建一个完整的 Kerberos 认证环境,包含三个组件:

  1. KDC(Key Distribution Center):用于签发 TGT
  2. Client(客户端):模拟用户请求服务
  3. Service(服务端):提供受保护的资源接口

我们使用 Spring Security Kerberos 提供的 MiniKdc 工具类,无需额外安装 Kerberos 服务,即可在本地运行一个嵌入式的 KDC,非常适合做集成测试。

3.1. 启动 KDC

使用 MiniKdc 启动一个嵌入式 KDC,代码如下:

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

MiniKdc.main(config);

该配置会创建一个 Realm 为 EXAMPLE.COM 的 KDC,并为客户端和服务端生成对应的 principal 和 keytab 文件:

  • client/localhost
  • HTTP/localhost

启动后,MiniKdc 会输出类似如下信息:

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]

⚠️ 注意:如果你使用的是 JDK 9 及以上版本,需要添加如下 JVM 参数,否则会报错:

--add-exports java.security.jgss/sun.security.krb5=ALL-UNNAMED

3.2. 客户端应用

客户端是一个 Spring Boot 应用,使用 KerberosRestTemplate 发起受 Kerberos 保护的 HTTP 请求:

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

调用服务的代码非常简单:

@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 支持,核心配置如下:

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

说明:上述配置中省略了具体的参数设置,完整配置可参考 Spring Security Kerberos 官方文档

4. 测试验证

接下来我们编写一个集成测试,验证客户端是否能通过 Kerberos 成功访问服务端接口。

✅ 正常测试(使用 KerberosRestTemplate)

@Autowired
private SampleClient sampleClient;

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

❌ 异常测试(不使用 KerberosRestTemplate)

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

⚠️ 注意:由于 HttpUrlConnection 默认会使用本地的凭证缓存(Credential Cache),有可能导致测试误判。可以添加如下 JVM 参数禁用缓存:

-Dhttp.use.global.creds=false

5. 小结

在本教程中,我们使用 Spring Security Kerberos 实现了一个完整的 Kerberos SSO 环境:

  • 使用 MiniKdc 快速搭建 KDC,无需依赖外部服务
  • 编写客户端和服务端,演示 Kerberos 的认证流程
  • 使用 KerberosRestTemplate 实现安全的 HTTP 请求
  • 编写集成测试验证 Kerberos 的有效性

虽然我们只是介绍了 Kerberos 的基本使用,但已经可以满足大多数 Spring 应用的 Kerberos 集成需求。

如需进一步了解 Kerberos 协议细节,可参考:

完整代码可在 GitHub 上查看:GitHub 示例地址


原始标题:Spring Security Kerberos Integration

« 上一篇: JPA 中的连接类型
» 下一篇: Spock 扩展指南