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 认证环境,包含三个组件:
- KDC(Key Distribution Center):用于签发 TGT
- Client(客户端):模拟用户请求服务
- 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 示例地址