跳到主要内容
版本:7.0.2

Spring 与 Spring Security Kerberos

DeepSeek V3 中英对照 Reference Spring and Spring Security Kerberos

参考文档的这一部分解释了 Spring Security Kerberos 为任何基于 Spring 的应用程序提供的核心功能。

身份验证提供程序 描述了身份验证提供程序的支持。

Spnego Negotiate 描述了 spnego negotiate 支持。

使用 KerberosRestTemplate 描述了 RestTemplate 支持。

认证提供程序

使用JavaConfig进行提供者配置。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

@Value("${app.service-principal}")
private String servicePrincipal;

@Value("${app.keytab-location}")
private String keytabLocation;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
KerberosAuthenticationProvider kerberosAuthenticationProvider = kerberosAuthenticationProvider();
KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider = kerberosServiceAuthenticationProvider();
ProviderManager providerManager = new ProviderManager(kerberosAuthenticationProvider,
kerberosServiceAuthenticationProvider);

http
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll()
.and()
.authenticationProvider(kerberosAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider())
.addFilterBefore(spnegoAuthenticationProcessingFilter(providerManager),
BasicAuthenticationFilter.class);
return http.build();
}

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

@Bean
public SpnegoEntryPoint spnegoEntryPoint() {
return new SpnegoEntryPoint("/login");
}

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

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

@Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(servicePrincipal);
ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
ticketValidator.setDebug(true);
return ticketValidator;
}

@Bean
public DummyUserDetailsService dummyUserDetailsService() {
return new DummyUserDetailsService();
}
}

Spnego 协商

使用JavaConfig配置Spnego。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

@Value("${app.ad-domain}")
private String adDomain;

@Value("${app.ad-server}")
private String adServer;

@Value("${app.service-principal}")
private String servicePrincipal;

@Value("${app.keytab-location}")
private String keytabLocation;

@Value("${app.ldap-search-base}")
private String ldapSearchBase;

@Value("${app.ldap-search-filter}")
private String ldapSearchFilter;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider = kerberosServiceAuthenticationProvider();
ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = activeDirectoryLdapAuthenticationProvider();
ProviderManager providerManager = new ProviderManager(kerberosServiceAuthenticationProvider,
activeDirectoryLdapAuthenticationProvider);

http
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll()
.and()
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider())
.addFilterBefore(spnegoAuthenticationProcessingFilter(providerManager),
BasicAuthenticationFilter.class);

return http.build();
}

@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
return new ActiveDirectoryLdapAuthenticationProvider(adDomain, adServer);
}

@Bean
public SpnegoEntryPoint spnegoEntryPoint() {
return new SpnegoEntryPoint("/login");
}

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

public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() throws Exception {
KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(ldapUserDetailsService());
return provider;
}

@Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(servicePrincipal);
ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
ticketValidator.setDebug(true);
return ticketValidator;
}

@Bean
public KerberosLdapContextSource kerberosLdapContextSource() throws Exception {
KerberosLdapContextSource contextSource = new KerberosLdapContextSource(adServer);
contextSource.setLoginConfig(loginConfig());
return contextSource;
}

public SunJaasKrb5LoginConfig loginConfig() throws Exception {
SunJaasKrb5LoginConfig loginConfig = new SunJaasKrb5LoginConfig();
loginConfig.setKeyTabLocation(new FileSystemResource(keytabLocation));
loginConfig.setServicePrincipal(servicePrincipal);
loginConfig.setDebug(true);
loginConfig.setIsInitiator(true);
loginConfig.afterPropertiesSet();
return loginConfig;
}

@Bean
public LdapUserDetailsService ldapUserDetailsService() throws Exception {
FilterBasedLdapUserSearch userSearch =
new FilterBasedLdapUserSearch(ldapSearchBase, ldapSearchFilter, kerberosLdapContextSource());
LdapUserDetailsService service =
new LdapUserDetailsService(userSearch, new ActiveDirectoryLdapAuthoritiesPopulator());
service.setUserDetailsMapper(new LdapUserDetailsMapper());
return service;
}
}

使用 KerberosRestTemplate

如果需要以编程方式访问受Kerberos保护的Web资源,我们可以使用KerberosRestTemplate,它继承自RestTemplate并在委托给实际RestTemplate方法之前执行必要的登录操作。基本上,您有几种配置此模板的选项。

  • 若想使用缓存的票据,请将 keyTabLocation 和 userPrincipal 留空。

  • 若想使用 keytab 文件,请设置 keyTabLocation 和 userPrincipal。

  • 若想自定义 Krb5LoginModule 选项,请使用 loginOptions。

  • 使用自定义的 httpClient。

使用票据缓存。

public void doWithTicketCache() {
KerberosRestTemplate restTemplate =
new KerberosRestTemplate();
restTemplate.getForObject("http://neo.example.org:8080/hello", String.class);
}

使用 keytab 文件。

public void doWithKeytabFile() {
KerberosRestTemplate restTemplate =
new KerberosRestTemplate("/tmp/user2.keytab", "user2@EXAMPLE.ORG");
restTemplate.getForObject("http://neo.example.org:8080/hello", String.class);
}

使用 LDAP 服务进行身份验证

在大多数示例中,我们使用 DummyUserDetailsService,因为一旦 Kerberos 认证成功,并不一定需要查询真实的用户详情,我们可以使用 Kerberos 主体信息来创建该虚拟用户。不过,有一种方法可以安全地访问 Kerberized LDAP 服务,并从那里查询用户详情。

KerberosLdapContextSource 可用于通过 Kerberos 协议绑定到 LDAP,这至少已被证实能与 Windows AD 服务良好协作。

@Value("${app.ad-server}")
private String adServer;

@Value("${app.service-principal}")
private String servicePrincipal;

@Value("${app.keytab-location}")
private String keytabLocation;

@Value("${app.ldap-search-base}")
private String ldapSearchBase;

@Value("${app.ldap-search-filter}")
private String ldapSearchFilter;

@Bean
public KerberosLdapContextSource kerberosLdapContextSource() {
KerberosLdapContextSource contextSource = new KerberosLdapContextSource(adServer);
SunJaasKrb5LoginConfig loginConfig = new SunJaasKrb5LoginConfig();
loginConfig.setKeyTabLocation(new FileSystemResource(keytabLocation));
loginConfig.setServicePrincipal(servicePrincipal);
loginConfig.setDebug(true);
loginConfig.setIsInitiator(true);
contextSource.setLoginConfig(loginConfig);
return contextSource;
}

@Bean
public LdapUserDetailsService ldapUserDetailsService() {
FilterBasedLdapUserSearch userSearch =
new FilterBasedLdapUserSearch(ldapSearchBase, ldapSearchFilter, kerberosLdapContextSource());
LdapUserDetailsService service = new LdapUserDetailsService(userSearch);
service.setUserDetailsMapper(new LdapUserDetailsMapper());
return service;
}
提示

当前配置的 Security Server Windows Auth Sample 示例会在通过 Kerberos 进行身份验证时,从 AD 查询用户详细信息。