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-UNNAMED3.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 的教程,它會提供更詳細的説明。