知識庫 / Spring RSS 訂閱

Spring Security 與 MiniKdc 的 Kerberos 集成

Security,Spring
HongKong
6
01:23 PM · Dec 06 ,2025

1. 概述

本教程將介紹 Spring Security Kerberos 的基本概念。

我們將編寫一個 Java Kerberos 客户端,使其能夠授權訪問我們的 Kerberos 認證的服務。同時,我們還將運行一個嵌入式密鑰分發中心(Key Distribution Center,KDC)以進行完整的、端到端的 Kerberos 認證。這一切都無需任何外部基礎設施支持,這要歸功於 Spring Security Kerberos

2. Kerberos及其優勢

Kerberos 是一種由 MIT 在 20 世紀 80 年代創建的網絡身份驗證協議,尤其適用於在網絡上集中身份驗證。

1987 年,MIT 將其發佈給 開源 社區,並且仍在積極開發中。2005 年,它被 IETF 標準化為 RFC 4120

通常,Kerberos 在企業環境中被使用。 在其中,它以一種方式安全環境,使得 用户不必單獨向每個服務進行身份驗證。 這種架構解決方案被稱為 單點登錄

簡單來説,Kerberos 是一種票務系統。 用户 一次進行身份驗證接收一張憑證授予票(TGT)。 然後,網絡基礎設施會用該 TGT 交換服務票據。 這些服務票據允許用户與基礎設施服務交互,只要 TGT 有效,通常為數小時。

因此,用户只需登錄一次非常棒。 但也有一個安全優勢: 在這種環境中,用户的密碼 不會通過網絡發送。 相反,Kerberos 使用它作為生成另一個密鑰的因素,該密鑰將用於消息加密和解密。

另一個優勢是,我們可以從一箇中心位置管理用户,例如由 LDAP 支持的一個位置。 因此,如果我們在集中數據庫中禁用給定用户的帳户,則我們將在基礎設施中撤銷其訪問權限。 因此,管理員不必在每個服務中單獨撤銷訪問權限。

《Spring 中的 SPNEGO/Kerberos 身份驗證介紹》提供對該技術的深入概述。

3. Kerberos 環境

因此,我們將創建一個用於使用 Kerberos 協議進行身份驗證的環境。該環境將由三個同時運行的獨立應用程序組成。

首先,我們將擁有一個密鑰分發中心,它將作為身份驗證點。接下來,我們將編寫客户端和服務器應用程序,並配置它們使用 Kerberos 協議。

現在,運行 Kerberos 需要一些安裝和配置。但是,我們將利用 Spring Security Kerberos,因此我們將以嵌入模式程序化運行密鑰分發中心。此外,下所示的 MiniKdc 在與 Kerberos 環境進行集成測試時非常有用。

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]

如果我們在 JDK 9 或更高版本中使用,則需要在我們的 IDE 中啓用 java.security.jgss 模塊,以便 MiniKdc 能夠成功啓動。可以通過在執行 Main 類期間設置的命令行參數來完成此操作:

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

3.2. 客户端應用程序

我們的客户端應用程序將是一個基於 Spring Boot 的應用程序,它將使用 RestTemplate 來調用外部的 REST API。

但是,我們將使用 KerberosRestTemplate 代替它。它需要密鑰表和客户端的 principal。

@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 協議的客户端部分。

所以,讓我們創建一個快速的類,用於從 Kerberos 服務查詢數據,該服務位於 app.access-url 端點。

@Service
class SampleClient {

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

    private RestTemplate restTemplate;

    // constructor, getter, setter

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

讓我們現在創建一個 Service 應用程序,以便這個類可以調用一些東西!

3.3. 服務應用程序

我們將使用 Spring Security,並配置相應的 Kerberos 特性 Bean。

此外,請注意,服務將擁有自己的 principal,並使用 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;
    }

}

引言文章包含了上述所有實現,因此為了簡明起見,我們省略了完整的實現方法。

請注意,我們已配置 Spring Security 用於 SPNEGO 身份驗證。 這樣,我們就可以通過 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 的情況下訪問服務來證明其重要性:

@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 的 維基頁面其 RFC。 官方文檔頁面 也會很有用。 此外,要了解在核心 Java 中如何實現這些功能,可以參考 Oracle 的教程,它會提供更詳細的説明。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.