Spring boot SAML 2 身份验证对象 null
Posted
技术标签:
【中文标题】Spring boot SAML 2 身份验证对象 null【英文标题】:Spring boot SAML 2 authentication object null 【发布时间】:2021-03-31 13:56:47 【问题描述】:我需要将 SAML 身份验证与 REST API 集成,以便我可以使我的 REST 服务无状态,我采取的方法如下
在 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 我已尝试将我尝试使其工作的所有配置都放入其中,因此希望在所有上下文中提供更好的理解 【参考方案1】:终于找到问题了。
在 Zuul 中,sensitiveHeaders 有一个默认值Cookie,Set-Cookie,Authorization
现在,如果我们不设置属性本身,那么这些标头将被视为敏感的,并且不会向下游传输到我们的服务。
必须将sensitiveHeaders 值设置为空,以便将cookies 传递给服务。 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
【讨论】:
以上是关于Spring boot SAML 2 身份验证对象 null的主要内容,如果未能解决你的问题,请参考以下文章
基于 SAML 的 SSO 用于身份验证和 LDAP 用于授权 - Spring Boot Security
即使在 IDP 使用 SAML 成功登录后,获取身份验证对象仍为空
如何在 Spring Boot 中从 Azure SAML 2.0 应用程序获取角色或组
没有 Spring Boot 的 Spring Security SAML 身份元数据