Spring Boot 应用程序中 upns AD 更改的 Kerberos SSO 问题

问题描述 投票:0回答:1

所以我有一个 Spring Boot 应用程序,它通过 kerberos 对其用户进行身份验证,然后对角色和权限等进行 ldap 搜索。它运行良好。问题是,AD 将会发生变化。假设用户名为 Jane Doe,她现在的 upn 是 [电子邮件受保护],它将更改为 [电子邮件受保护]。现在,像我这样的用户已经出于测试目的而进行了更改,但 SSO 对我不起作用,我不明白为什么。让我们说我是简·无名氏,所以它在日志中这么说

2024-04-17 16:10:16,448 WARN  [https-jsse-nio-10.100.147.15-8443-exec-10][o.s.s.k.w.a.SpnegoAuthenticationProcessingFilter] Negotiate Header was invalid: Negotiate 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 {span_id=8b7b8299baf54a54, trace_flags=01, trace_id=c316521436a8f809253b89209d5147ff}
org.springframework.security.core.userdetails.UsernameNotFoundException: User [email protected] not found in directory.

这是有道理的,因为我现在[电子邮件受保护]。但旧的 upn 从哪里来呢?我不知道去哪里看。

哦,这是 springboot 应用程序中的设置

@Configuration
@EnableWebMvc
@ImportResource({"classpath*:spring/**/*applicationContext.xml"})
@ComponentScan(basePackages = {"com.abc"})
public class WebConfiguration implements WebMvcConfigurer {
  private ApplicationContext applicationContext;

  @Autowired
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  @Override
  public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {

//    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
  //  dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

    ObjectMapper dateFormatMapper = new ObjectMapper();
    dateFormatMapper.registerModule(new JavaTimeModule());
    dateFormatMapper/*.findAndRegisterModules()*/.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//    dateFormatMapper.setDateFormat(dateFormat);
    converters.add(new MappingJackson2HttpMessageConverter(dateFormatMapper));


    converters.add(new ByteArrayHttpMessageConverter());
    converters.add(new StringHttpMessageConverter());
    converters.add(new ResourceHttpMessageConverter());
    converters.add(new FormHttpMessageConverter());
    /*
    SourceHttpMessageConverter – converts javax.xml.transform.Source
    FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String>.
        Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML (added only if JAXB2 is present on the classpath)
    MappingJackson2HttpMessageConverter – converts JSON (added only if Jackson 2 is present on the classpath)
    MappingJacksonHttpMessageConverter – converts JSON (added only if Jackson is present on the classpath)
    AtomFeedHttpMessageConverter – converts Atom feeds (added only if Rome is present on the classpath)
    RssChannelHttpMessageConverter – converts RSS feeds (added only if Rome is present on the classpath)
*/
  }

  @EventListener(ContextRefreshedEvent.class)
  public void onContextRefreshed() {
    final DbMigrateService migrateService = applicationContext.getBean(DbMigrateService.class);
    migrateService.migrateIfNecessary();
    final ReportingDbMigrateService reportingMigrateService =
        applicationContext.getBean(ReportingDbMigrateService.class);
    reportingMigrateService.migrateIfNecessary();
  }
}




package com.abc.def.service.config;


import com.abc.def.service.ProductionSelector;
import com.abc.def.service.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jndi.JndiTemplate;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider;
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator;
import org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig;
import org.springframework.security.kerberos.client.ldap.KerberosLdapContextSource;
import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter;
import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.ldap.userdetails.LdapUserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsUtils;

import javax.naming.NamingException;

@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Value("DC=abc,DC=com")
  private String rootDN;

  @Value("/apps/HTTP.keytab")
  private String keytabLocation;

  @Value("DC=abc,DC=com")
  private String searchBase;

  @Value("(| (userPrincipalName={0}) (sAMAccountName={0}))")
  private String searchFilter;

  @Value("OU=GroupsAdministrative,OU=GE,DC=abc,DC=com")
  private String groupSearchBase;

  @Value("cn")
  private String groupRoleAttribute;

  @Value("(member={0})")
  private String groupSearchFilter;

  @Override
  protected void configure(HttpSecurity http) throws Exception {

    CookieCsrfTokenRepository cookieCsrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();

    // this is needed because of two applications.
    cookieCsrfTokenRepository.setCookiePath("/");

    boolean production = ProductionSelector.isProduction();

    http
        .csrf().csrfTokenRepository((cookieCsrfTokenRepository))
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(spnegoEntryPoint())
        .and()
        .authorizeRequests()
        .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
        .antMatchers("/ownbank/legal").hasAuthority(User.getOwnBankLegalPermission(production))
        .antMatchers("/ownbank/organisational").hasAuthority(User.getOwnBankOrganisationalPermission(production))
        .antMatchers("/ownbank/finance").hasAuthority(User.getOwnBankFinancePermission(production))
        .antMatchers("/calendar/*").hasAuthority(User.getWritePermission(production))
        .antMatchers("/compatibl/*").hasAnyAuthority(User.getCompatiblReadPermission(production),
            User.getCompatiblWritePermission(production))
        .antMatchers("/healthcheck/*").hasAnyAuthority(User.getHealthCheckReadPermission(production), User.getHealthCheckWritePermission(production))
        .antMatchers("/s2s/dump-config").hasAnyAuthority(User.getS2SWritePermission(production))
        .antMatchers("/s2s/run", "/s2s/upload/**").hasAnyAuthority(User.getS2SWritePermission(production))
        .antMatchers("/s2s/sap-dumps", "/s2s/summit-dumps", "/s2s/processing-events", "/s2s/processing-events-summary",
            "/s2s/download-processing-events", "/s2s/audit-records", "/s2s/download-sap-dump",
            "/s2s/download-summit-dump", "/s2s/download/**", "/s2s/replay", "/s2s/processing-steps",
            "/s2s/summit-dump-counts", "/s2s/start-diff", "/s2s/diffs", "/s2s/diffs/**", "/s2s/diffGroup",
            "/s2s/tradeDiff"
        ).hasAnyAuthority(User.getS2SReadPermission(production), User.getS2SWritePermission(production))
        .anyRequest().authenticated()
        .and()
        .addFilterBefore(spnegoAuthenticationProcessingFilter(authenticationManagerBean()),
            BasicAuthenticationFilter.class);
  }

  @Override
  protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
    UserDetailsService userDetailsService = ldapUserDetailsService();
    authManagerBuilder
        .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
        .authenticationProvider(kerberosServiceAuthenticationProvider(userDetailsService))
        .userDetailsService(userDetailsService);
  }

  @Bean
  @Override
  public UserDetailsService userDetailsServiceBean() throws Exception {
    return super.userDetailsServiceBean();
  }

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

  @Bean
  public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
    return new ActiveDirectoryLdapAuthenticationProvider("", getAdServerUrl(),rootDN);
  }


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


  @Bean
  public AuthenticationProvider kerberosServiceAuthenticationProvider(UserDetailsService userDetailsService) {
    KerberosServiceAuthenticationProvider provider = new org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider();
    provider.setTicketValidator(getTicketValidator());
    provider.setUserDetailsService(userDetailsService);

    return provider;
  }

  @Bean
  public SunJaasKerberosTicketValidator getTicketValidator() {
    SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();

    log.info("service principal path for Kerberos => {}", getAdServicePrincipal());

    ticketValidator.setServicePrincipal(getAdServicePrincipal());
    ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
//    ticketValidator.setDebug(true);
    return ticketValidator;
  }

  @Bean
  public UserDetailsService ldapUserDetailsService() {

    BaseLdapPathContextSource contextSource = ldapContextSource();
    FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(searchBase, searchFilter, contextSource);
    userSearch.setSearchSubtree(true);
    LdapUserDetailsService ldapUserDetailsService = new LdapUserDetailsService(userSearch,
        ldapAuthoritiesPopulator(contextSource));
    ldapUserDetailsService.setUserDetailsMapper(new LdapUserDetailsMapper());

    return ldapUserDetailsService;
  }

  @Bean
  public LdapAuthoritiesPopulator ldapAuthoritiesPopulator(ContextSource contextSource) {

    DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
    populator.setSearchSubtree(true);
    populator.setGroupRoleAttribute(groupRoleAttribute);
    populator.setGroupSearchFilter(groupSearchFilter);
    populator.setRolePrefix("");
    populator.setConvertToUpperCase(false);
    return populator;
  }

  @Bean
  public KerberosLdapContextSource ldapContextSource() {
    KerberosLdapContextSource source = new KerberosLdapContextSource(getAdServerUrl());
    source.setLoginConfig(getSunJaasKrb5LoginConfig());
    return source;
  }

  @Bean
  public SunJaasKrb5LoginConfig getSunJaasKrb5LoginConfig() {
    SunJaasKrb5LoginConfig config = new SunJaasKrb5LoginConfig();
    config.setServicePrincipal(getAdServicePrincipal());
    config.setKeyTabLocation(new FileSystemResource(keytabLocation));
    config.setUseTicketCache(false);
    config.setIsInitiator(true);

    //config.setDebug(true);
    return config;
  }

  private String getAdServerUrl() {

    JndiTemplate jndi = new JndiTemplate();
    try {
      return (String) jndi.lookup("java:/comp/env/ad.server");
    } catch (NamingException e) {
      throw new RuntimeException(e);
    }
  }

  private String getAdServicePrincipal() {
    JndiTemplate jndi = new JndiTemplate();
    try {
      return (String) jndi.lookup("java:/comp/env/ad.service.principal");
    } catch (NamingException e) {
      throw new RuntimeException(e);
    }
  }
}
java spring active-directory single-sign-on kerberos
1个回答
0
投票

这不是 UPN,而是 Kerberos 主体 名称。它是 Kerberos 中的主要用户(和服务)标识符,它仅与 UPN 类似,但除此之外两者之间没有任何关系。

(AD 发出的票证也包含 UPN,但仅作为 MS PAC 中的附加字段,并且大多数软件不知道如何从 PAC 中提取任何内容。)

在 Active Directory 中,用户 Kerberos 主体由

account@REALM
构成,其中前半部分始终是用户的 sAMAccountName,后半部分是 Kerberos 领域名称(每个 AD 域只有一个 Kerberos 领域,并且始终位于上层)大小写,与通常为小写的 UPN 后缀不同)。您可以从
klist
看到您的。

因此,如果没有域信任,则可以安全地修剪

@REALM
并搜索与用户主体前半部分匹配的 sAMAccountName。根据this,Spring可以使用
(sAMAccountName={1})
来做到这一点。

© www.soinside.com 2019 - 2024. All rights reserved.