Spring boot SAML 2 authentication object null

允我心安 提交于 2021-01-28 21:10:52

问题


I've a requirement to integrate SAML authentication with rest API, so that I can make my rest services stateless, the approach which I've taken is as follows

  • Developed an authentication service behind zuul proxy which is running behind AWS ALB
  • User tries to generate token via endpoint https://my-domain/as/auth/login
  • Since user is not logged in, so he gets redirected to IDP where he authenticate
  • After authentication the IDP redirect user back to my service i.e. at URL https://my-domain/as/auth/login
  • I check for user authentication principal and if the user is authenticated then I generate JWT token and return it to user

SAML authentication works well, the issue is when the user is redirected back to success URL i.e. https://my-domain/as/auth/login then the authentication object is null because the SecurityContextHolder is cleared after successful authentication and 401 handler kicks in and redirect user to IDP in loop until SAML assertion is failed, please suggest where I'm mistaking

My Zuul proxy config looks like below

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

My SAML security config looks like below

@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("/");
  }

回答1:


Finally found out the issue.

In Zuul, the sensitiveHeaders has got a default value of Cookie,Set-Cookie,Authorization

Now if we dont set the property itself, then these headers are going to be treated as sensitive and doesnt get flown downstream to our service.

Had to set the the sensitiveHeaders value to empty so that the cookies get passed on to the service. Cookie contains our JSESSIONID which has identifies the session.

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


来源:https://stackoverflow.com/questions/65398941/spring-boot-saml-2-authentication-object-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!