Объект аутентификации Spring boot SAML 2 null

У меня есть требование интегрировать аутентификацию SAML с остальным API, чтобы я мог сделать мои остальные службы безгражданскими, подход, который я использовал, выглядит следующим образом.

  • Разработан сервис аутентификации за прокси-сервером zuul, который работает за AWS ALB.
  • Пользователь пытается сгенерировать токен через конечную точку https://my-domain/as/auth/login.
  • Поскольку пользователь не вошел в систему, он перенаправляется на IDP, где он аутентифицируется
  • После аутентификации IDP перенаправляет пользователя обратно в мою службу, то есть по URL-адресу https://my-domain/as/auth/login.
  • Я проверяю принцип аутентификации пользователя, и если пользователь аутентифицирован, я генерирую токен JWT и возвращаю его пользователю.

Аутентификация SAML работает хорошо, проблема заключается в том, что пользователь перенаправляется обратно на успешный URL-адрес, т. Е. https://my-domain/as/auth/login, тогда объект аутентификации имеет значение null, потому что SecurityContextHolder очищается после успешной аутентификации, и обработчик 401 срабатывает и перенаправляет пользователя на IDP в цикле до тех пор, пока утверждение SAML не будет отклонено. , пожалуйста, подскажите, где я ошибаюсь

Конфигурация моего прокси-сервера Zuul выглядит следующим образом:

zuul:
   ribbon:
      eager-load:
         enabled: true
   host:
      connect-timeout-millis: 5000000
      socket-timeout-millis: 5000000
   ignoredServices: ""
   ignoredPatterns:
      - /as/*
   routes:
      sensitiveHeaders: Cookie,Set-Cookie
      import-data-service:
         path: /ids/*
         serviceId: IDS
         stripPrefix: true
      authentication-service:
         path: /as/*
         serviceId: AS
         stripPrefix: true
         customSensitiveHeaders: false

Моя конфигурация безопасности SAML выглядит следующим образом:

@Configuration
public class SamlSecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public WebSSOProfileOptions defaultWebSSOProfileOptions() {
    WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
    webSSOProfileOptions.setIncludeScoping(false);
    webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
    return webSSOProfileOptions;
  }

  @Bean
  public SAMLEntryPoint samlEntryPoint() {
    SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
    samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
    return samlEntryPoint;
  }

  @Bean
  public MetadataDisplayFilter metadataDisplayFilter() {
    return new MetadataDisplayFilter();
  }

  @Bean
  public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
    return new SimpleUrlAuthenticationFailureHandler();
  }


  @Bean
  public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
    SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
        new SavedRequestAwareAuthenticationSuccessHandler();
    successRedirectHandler.setDefaultTargetUrl("https://my-domain/as/saml/SSO");
    return successRedirectHandler;
  }

  @Bean
  public SessionRepositoryFilter sessionFilter() {
    HttpSessionStrategy cookieStrategy = new CookieHttpSessionStrategy();
    MapSessionRepository repository = new MapSessionRepository();
    ((CookieHttpSessionStrategy) cookieStrategy).setCookieName("JSESSIONID");
    SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(repository);
    sessionRepositoryFilter.setHttpSessionStrategy(cookieStrategy);
    return sessionRepositoryFilter;
  }


  @Bean
  public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
    SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
    samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
    samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
    samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
    return samlWebSSOProcessingFilter;
  }


  @Bean
  public HttpStatusReturningLogoutSuccessHandler successLogoutHandler() {
    return new HttpStatusReturningLogoutSuccessHandler();
  }

  @Bean
  public SecurityContextLogoutHandler logoutHandler() {
    SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
    logoutHandler.setInvalidateHttpSession(true);
    logoutHandler.setClearAuthentication(true);
    return logoutHandler;
  }

  @Bean
  public SAMLLogoutFilter samlLogoutFilter() {
    return new SAMLLogoutFilter(successLogoutHandler(), new LogoutHandler[] {logoutHandler()},
        new LogoutHandler[] {logoutHandler()});
  }

  @Bean
  public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
    return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
  }

  @Bean
  public MetadataGeneratorFilter metadataGeneratorFilter() {
    return new MetadataGeneratorFilter(metadataGenerator());
  }

  @Bean
  public MetadataGenerator metadataGenerator() {
    MetadataGenerator metadataGenerator = new MetadataGenerator();
    metadataGenerator.setEntityId("entityUniqueIdenifier");
    metadataGenerator.setExtendedMetadata(extendedMetadata());
    metadataGenerator.setIncludeDiscoveryExtension(false);
    metadataGenerator.setRequestSigned(true);
    metadataGenerator.setKeyManager(keyManager());
    metadataGenerator.setEntityBaseURL("https://my-domain/as");
    return metadataGenerator;
  }

  @Bean
  public KeyManager keyManager() {
    ClassPathResource storeFile = new ClassPathResource("/saml-keystore.jks");
    String storePass = "samlstorepass";
    Map<String, String> passwords = new HashMap<>();
    passwords.put("mykeyalias", "mykeypass");
    return new JKSKeyManager(storeFile, storePass, passwords, "mykeyalias");
  }

  @Bean
  public ExtendedMetadata extendedMetadata() {
    ExtendedMetadata extendedMetadata = new ExtendedMetadata();
    extendedMetadata.setIdpDiscoveryEnabled(false);
    extendedMetadata.setSignMetadata(false);
    extendedMetadata.setSigningKey("mykeyalias");
    extendedMetadata.setEncryptionKey("mykeyalias");
    return extendedMetadata;
  }

  @Bean
  public FilterChainProxy samlFilter() throws Exception {
    List<SecurityFilterChain> chains = new ArrayList<>();

    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
        metadataDisplayFilter()));

    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
        samlEntryPoint()));

    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
        samlWebSSOProcessingFilter()));

    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
        samlWebSSOHoKProcessingFilter()));

    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
        samlLogoutFilter()));

    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
        samlLogoutProcessingFilter()));

    return new FilterChainProxy(chains);
  }

  @Bean
  public TLSProtocolConfigurer tlsProtocolConfigurer() {
    return new TLSProtocolConfigurer();
  }

  @Bean
  public ProtocolSocketFactory socketFactory() {
    return new TLSProtocolSocketFactory(keyManager(), null, "default");
  }

  @Bean
  public Protocol socketFactoryProtocol() {
    return new Protocol("https", socketFactory(), 443);
  }

  @Bean
  public MethodInvokingFactoryBean socketFactoryInitialization() {
    MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
    methodInvokingFactoryBean.setTargetClass(Protocol.class);
    methodInvokingFactoryBean.setTargetMethod("registerProtocol");
    Object[] args = {"https", socketFactoryProtocol()};
    methodInvokingFactoryBean.setArguments(args);
    return methodInvokingFactoryBean;
  }

  @Bean
  public VelocityEngine velocityEngine() {
    return VelocityFactory.getEngine();
  }

  @Bean(initMethod = "initialize")
  public StaticBasicParserPool parserPool() {
    return new StaticBasicParserPool();
  }

  @Bean(name = "parserPoolHolder")
  public ParserPoolHolder parserPoolHolder() {
    return new ParserPoolHolder();
  }

  @Bean
  public HTTPPostBinding httpPostBinding() {
    return new HTTPPostBinding(parserPool(), velocityEngine());
  }

  @Bean
  public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
    return new HTTPRedirectDeflateBinding(parserPool());
  }

  @Bean
  public SAMLProcessorImpl processor() {
    Collection<SAMLBinding> bindings = new ArrayList<>();
    bindings.add(httpRedirectDeflateBinding());
    bindings.add(httpPostBinding());
    return new SAMLProcessorImpl(bindings);
  }

  @Bean
  public HttpClient httpClient() {
    return new HttpClient(multiThreadedHttpConnectionManager());
  }

  @Bean
  public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
    return new MultiThreadedHttpConnectionManager();
  }

  @Bean
  public static SAMLBootstrap sAMLBootstrap() {
    return new CustomSamlBootStrap();
  }

  @Bean
  public SAMLDefaultLogger samlLogger() {
    SAMLDefaultLogger samlDefaultLogger = new SAMLDefaultLogger();
    samlDefaultLogger.setLogAllMessages(true);
    samlDefaultLogger.setLogErrors(true);
    return samlDefaultLogger;
  }

  @Bean
  public SAMLContextProviderImpl contextProvider() {
    SAMLContextProviderLB samlContextProviderLB = new SAMLContextProviderLB();
    samlContextProviderLB.setServerName("my-domain/as");
    samlContextProviderLB.setScheme("https");
    samlContextProviderLB.setServerPort(443);
    samlContextProviderLB.setIncludeServerPortInRequestURL(false);
    samlContextProviderLB.setContextPath("");
    // samlContextProviderLB.setStorageFactory(new EmptyStorageFactory());
    return samlContextProviderLB;
  }

  // SAML 2.0 WebSSO Assertion Consumer
  @Bean
  public WebSSOProfileConsumer webSSOprofileConsumer() {
    return new WebSSOProfileConsumerImpl();
  }

  // SAML 2.0 Web SSO profile
  @Bean
  public WebSSOProfile webSSOprofile() {
    return new WebSSOProfileImpl();
  }

  // not used but autowired...
  // SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
  @Bean
  public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
    return new WebSSOProfileConsumerHoKImpl();
  }

  // not used but autowired...
  // SAML 2.0 Holder-of-Key Web SSO profile
  @Bean
  public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
    return new WebSSOProfileConsumerHoKImpl();
  }

  @Bean
  public SingleLogoutProfile logoutprofile() {
    return new SingleLogoutProfileImpl();
  }

  @Bean
  public ExtendedMetadataDelegate idpMetadata()
      throws MetadataProviderException, ResourceException {

    Timer backgroundTaskTimer = new Timer(true);

    ResourceBackedMetadataProvider resourceBackedMetadataProvider =
        new ResourceBackedMetadataProvider(backgroundTaskTimer,
            new ClasspathResource("/IDP-metadata.xml"));

    resourceBackedMetadataProvider.setParserPool(parserPool());

    ExtendedMetadataDelegate extendedMetadataDelegate =
        new ExtendedMetadataDelegate(resourceBackedMetadataProvider, extendedMetadata());
    extendedMetadataDelegate.setMetadataTrustCheck(true);
    extendedMetadataDelegate.setMetadataRequireSignature(false);
    return extendedMetadataDelegate;
  }

  @Bean
  @Qualifier("metadata")
  public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException {
    List<MetadataProvider> providers = new ArrayList<>();
    providers.add(idpMetadata());
    return new CachingMetadataManager(providers);
  }

  @Bean
  public SAMLUserDetailsService samlUserDetailsService() {
    return new SamlUserDetailsServiceImpl();
  }

 @Bean
  public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
    final SAMLWebSSOHoKProcessingFilter filter = new SAMLWebSSOHoKProcessingFilter();
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(successRedirectHandler());
    filter.setAuthenticationFailureHandler(authenticationFailureHandler());
    return filter;
  }


  @Bean
  public SAMLAuthenticationProvider samlAuthenticationProvider() {
    SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
    samlAuthenticationProvider.setUserDetails(samlUserDetailsService());
    samlAuthenticationProvider.setForcePrincipalAsString(false);
    return samlAuthenticationProvider;
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(samlAuthenticationProvider());
  }

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

    http.exceptionHandling().authenticationEntryPoint(samlEntryPoint());
    http.csrf().disable();
    http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
        .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);

    http.authorizeRequests().antMatchers("/error").permitAll().antMatchers("/saml/**").permitAll()
        .anyRequest().authenticated();

    http.logout().logoutSuccessUrl("/");
  }

У вас будет больше шансов получить ответ, если вы предоставите mvce

s7vr 28.12.2020 15:33

@ s7vr Я пытался настроить все конфигурации, которые я пытался заставить работать, поэтому хотел дать весь контекст для лучшего понимания.

Apollo 29.12.2020 06:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
1
2
1 258
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Наконец-то узнал проблему.

В Zuul чувствительные заголовки имеют значение по умолчанию Cookie,Set-Cookie,Authorization

Теперь, если мы не установим само свойство, эти заголовки будут рассматриваться как конфиденциальные и не будут переданы в нашу службу.

Пришлось установить значение чувствительностиHeaders пустым, чтобы файлы cookie передавались службе. Cookie содержит наш JSESSIONID, который идентифицирует сеанс.

zuul:
   ribbon:
      eager-load:
         enabled: true
   host:
      connect-timeout-millis: 5000000
      socket-timeout-millis: 5000000
   ignoredServices: ""
   sensitiveHeaders: 
   ignoredPatterns:
      - /as/*
   routes:
      import-data-service:
         path: /ids/*
         serviceId: IDS
         stripPrefix: true
      authentication-service:
         path: /as/*
         serviceId: AS
         stripPrefix: true
         customSensitiveHeaders: false

Другие вопросы по теме