1. 概述

在本教程中,我们将了解 Kerberos 认证协议的基础知识,并探讨为何在 Kerberos 中需要 SPNEGO。

接着,我们将使用 Spring Security 的 Kerberos 扩展模块,快速搭建一个支持 Kerberos + SPNEGO 的 Web 应用。

⚠️ 本文假设读者对认证机制、Spring Security 和 Web 应用有基本了解。我们将跳过一些基础内容,聚焦在 Kerberos 与 SPNEGO 的整合与实践上。


2. 理解 Kerberos

Kerberos 是一种网络认证协议,由 MIT 在上世纪 80 年代开发。虽然它诞生已久,但至今仍被广泛使用,尤其在 Windows 环境中是默认的认证机制。

Kerberos 是基于票据(Ticket)的认证协议,允许网络中的节点互相验证身份。

2.1. Kerberos 的一个简单使用场景

假设一个用户想通过邮件客户端从邮件服务器拉取邮件。为了安全通信,客户端和服务器之间需要互相认证。

Kerberos 引入了一个第三方角色:KDC(Key Distribution Centre),它与网络中的每个节点都建立信任关系。通过 KDC,节点之间无需直接交换密码即可完成认证。

Kerberos 协议流程图

2.2. Kerberos 协议的核心特性

  • 所有节点(客户端和服务器)信任 KDC
  • 密码从不在网络上传输
  • 客户端与服务器之间的信任是基于它们都能用与 KDC 共享的密钥解密消息
  • 客户端与服务器之间是双向认证
  • 客户端可缓存票据,实现单点登录(SSO)
  • 使用时间戳生成的一次性 Authenticator 消息
  • 所有三方时间需大致同步

虽然这些只是 Kerberos 的冰山一角,但已经足够帮助我们继续本教程。


3. 理解 SPNEGO

SPNEGO 全称 Simple and Protected GSS-API Negotiation Mechanism,是 GSS-API 的一部分。

GSS-API(Generic Security Service Application Program Interface)是 IETF 标准,用于客户端与服务端之间以安全、厂商无关的方式通信。

✅ SPNEGO 的作用是:让客户端和服务端协商使用哪种安全机制,比如 Kerberos 或 NTLM。


4. 为什么在 Kerberos 中需要 SPNEGO?

Kerberos 是一个运行在传输层的协议(如 TCP/UDP),适合传统网络服务(如 Telnet、FTP 等)。但 Web 应用运行在更高层(HTTP),无法直接使用 Kerberos。

这时 SPNEGO 就派上用场了。它使得浏览器和 Web 服务器之间可以通过 HTTP 协议交换 Kerberos 票据,将 Kerberos 票据封装为 SPNEGO Token,通过 HTTP Header 传输

Kerberos 与 SPNEGO 协议流程图

✅ 流程简要说明:

  • 客户端向 KDC 认证并缓存 TGT(Ticket Granting Ticket)
  • 浏览器配置为支持 SPNEGO 和 Kerberos
  • Web 应用也配置为支持 SPNEGO 和 Kerberos
  • 浏览器访问受保护资源时,服务器返回 “Negotiate” 挑战
  • 浏览器将 Service Ticket 封装为 SPNEGO Token,通过 HTTP Header 发送

5. 前置准备

要搭建支持 Kerberos 的 Web 应用,我们需要完成以下准备工作:

5.1. 配置 KDC

Kerberos 的生产环境配置不在本教程范围内,但我们可以使用以下开源实现:

  • MIT Kerberos v5
  • Apache Kerby(支持 Java)
  • Windows Server Active Directory
  • Heimdal Kerberos

建议使用 Docker 运行 Apache Kerby 快速搭建测试环境。

5.2. 在 KDC 中创建用户(Principal)

我们需创建两个 Principal:

$ kadmin: addprinc -randkey kchandrakant -pw password
Principal "[email protected]" created.

注册 Web 应用:

$ kadmin: addprinc -randkey HTTP/[email protected] -pw password
Principal "HTTP/[email protected]" created.

⚠️ 注意命名规范:HTTP/<域名>@REALM,这与浏览器自动生成的 SPN 匹配。

导出 keytab 文件供 Web 应用使用:

$ kadmin: ktadd -k baeldung.keytab HTTP/[email protected]

5.3. 浏览器配置

现代浏览器(如 Chrome)默认支持 SPNEGO。我们可以配置浏览器启用集成认证,但为了演示清晰,建议手动输入用户名密码。

5.4. 域名配置

Kerberos 不支持 localhost 或 IP 地址。解决方法是配置 hosts 文件:

127.0.0.1 demo.kerberos.bealdung.com

6. 使用 Spring Security 实现 SPNEGO + Kerberos

Spring Security 提供了 Kerberos 扩展模块,可非常方便地集成 Kerberos + SPNEGO。

6.1. Maven 依赖

<dependency>
    <groupId>org.springframework.security.kerberos</groupId>
    <artifactId>spring-security-kerberos-web</artifactId>
    <version>${kerberos.extension.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security.kerberos</groupId>
    <artifactId>spring-security-kerberos-client</artifactId>
    <version>${kerberos.extension.version}</version>
</dependency>

6.2. SPNEGO Filter 配置

在 Spring Security 中添加 SPNEGO Filter:

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

定义 Filter Bean:

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

6.3. Kerberos 认证配置

配置 AuthenticationManager:

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

配置 KerberosAuthenticationProvider:

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

配置 KerberosServiceAuthenticationProvider:

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

配置 Kerberos Ticket Validator:

@Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
    SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
    ticketValidator.setServicePrincipal("HTTP/[email protected]");
    ticketValidator.setKeyTabLocation(new FileSystemResource("baeldung.keytab"));
    return ticketValidator;
}

6.4. UserDetailsService 的作用

Kerberos 只负责身份认证,不提供用户授权信息。因此我们需要实现 UserDetailsService 来补充用户权限信息。

你可以简单实现一个:

@Bean
public UserDetailsService userDetailsService() {
    return username -> new User(username, "", AuthorityUtils.createAuthorityList("ROLE_USER"));
}

6.5. 启动应用

启动应用后,访问任意页面应触发浏览器弹出认证框,输入 Kerberos 用户名密码后,浏览器会自动构建 SPNEGO Token 并发送给服务端完成认证。

⚠️ 踩坑提醒:Kerberos 对配置非常敏感,比如域名、Principal 名、时间同步、keytab 文件路径等,稍有错误就会认证失败,建议逐一排查。


7. 实际应用场景

虽然 Kerberos 看起来有些“古老”,但它在企业内部系统中依然非常实用,尤其是:

  • 企业内部已有 Kerberos/AD 基础设施
  • 多个 Web 应用希望接入统一认证体系
  • 实现浏览器自动登录(Integrated Authentication)

✅ 使用 SPNEGO + Kerberos 可以实现浏览器与 Web 应用的无缝认证,无需用户重复输入账号密码,用户体验优秀。


8. 小结

本文我们了解了 Kerberos 的基本原理,以及 SPNEGO 在 Web 应用中如何帮助我们实现 Kerberos 认证。

我们还通过 Spring Security 快速搭建了一个支持 SPNEGO + Kerberos 的 Web 应用,涵盖了配置 Kerberos Principal、keytab 文件、Spring Security 配置等关键步骤。

虽然 Kerberos 的配置过程较为繁琐,但它在企业级认证中依然具有不可替代的地位。


完整代码示例可参考:GitHub

如需进一步学习 Kerberos 和 SPNEGO,建议阅读以下资料:


原始标题:Introduction to SPNEGO/Kerberos Authentication in Spring