如何为密钥持有者配置文件配置 Spring Security SAML 扩展
Posted
技术标签:
【中文标题】如何为密钥持有者配置文件配置 Spring Security SAML 扩展【英文标题】:How to configure Spring Security SAML extension for Holder-of-key profile 【发布时间】:2015-12-24 20:01:54 【问题描述】:我尝试使用 Spring Security SAML 扩展的 SAML Holder-of-key 配置文件,但没有成功,我什至谷歌了它,但没有找到任何相关文档或示例。 p>
我在 tomcat 配置(server.xml 文件)中添加了以下标签以启用 HTTPS 方案和客户端身份验证:
<Connector port="8443" SSLEnabled="true" scheme="https" secure="true" clientAuth="want" sslProtocol="TLS" keystoreFile="ks.keystore" keystorePass="password" truststoreFile="ts.keystore" truststorePass="password"/>
此配置似乎有效,因为当我访问我的应用程序网站时,它会请求客户端证书并将 AuthN 请求发送到 IdP。 IdP 验证请求但不要求客户端证书,作为响应,它返回一个没有 HoK 主题确认的断言,我收到以下错误:
org.springframework.security.authentication.AuthenticationServiceException: Error validating SAML message
at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:95)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:167)
at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:87)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:184)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.channel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:152)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.saml.metadata.MetadataGeneratorFilter.doFilter(MetadataGeneratorFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:75)
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:62)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:221)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:107)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:504)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:76)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:934)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:90)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:515)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1012)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:642)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1555)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Caused by: org.opensaml.common.SAMLException: Response doesn't have any valid assertion which would pass subject validation
at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:229)
at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:84)
... 43 more
Caused by: org.opensaml.common.SAMLException: Assertion invalidated by subject confirmation - can't be confirmed by holder-of-key method
at org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl.verifySubject(WebSSOProfileConsumerHoKImpl.java:150)
at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertion(WebSSOProfileConsumerImpl.java:296)
at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:214)
... 44 more
这是我的 Spring Security 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<context:annotation-config/>
<security:debug/>
<context:component-scan base-package="org.springframework.security.saml" />
<security:http entry-point-ref="samlEntryPoint" use-expressions="false">
<security:csrf disabled="true" />
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" requires-channel="https" />
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
<security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter" />
</security:http>
<!-- Filter automatically generates default SP metadata -->
<bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
<constructor-arg>
<bean class="org.springframework.security.saml.metadata.MetadataGenerator">
<property name="bindingsHoKSSO">
<list>
<value>post</value>
<value>artifact</value>
</list>
</property>
<property name="entityBaseURL" value="https://localhost:8443" />
<property name="extendedMetadata">
<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
<property name="signMetadata" value="true"/>
<property name="idpDiscoveryEnabled" value="true"/>
</bean>
</property>
</bean>
</constructor-arg>
</bean>
<bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
<security:filter-chain-map request-matcher="ant">
<security:filter-chain pattern="/saml/login/**" filters="samlEntryPoint" />
<security:filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter" />
<security:filter-chain pattern="/saml/HoKSSO" filters="samlWebSSOHoKProcessingFilter" />
<security:filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
<security:filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
<security:filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter" />
<security:filter-chain pattern="/saml/single_logout/**" filters="samlLogoutProcessingFilter" />
</security:filter-chain-map>
</bean>
<!-- LOGOUT -->
<!-- (Local Logout) Override default logout processing filter with the one processing SAML messages -->
<bean id="samlLogoutFilter" class="org.springframework.security.saml.SAMLLogoutFilter">
<constructor-arg index="0" ref="successLogoutHandler"/>
<constructor-arg index="1" ref="logoutHandler"/>
<constructor-arg index="2" ref="logoutHandler"/>
</bean>
<!-- (Single Logout) Filter processing incoming logout messages -->
<!-- First argument determines URL user will be redirected to after successful global logout -->
<bean id="samlLogoutProcessingFilter" class="org.springframework.security.saml.SAMLLogoutProcessingFilter">
<constructor-arg index="0" ref="successLogoutHandler"/>
<constructor-arg index="1" ref="logoutHandler"/>
</bean>
<!-- Handler for successful logout -->
<bean id="successLogoutHandler" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
<property name="defaultTargetUrl" value="/logout.jsp"/>
</bean>
<!-- Logout handler terminating local session -->
<bean id="logoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
<property name="invalidateHttpSession" value="false"/>
</bean>
<!-- SAML 2.0 Logout Profile -->
<bean id="logoutprofile" class="org.springframework.security.saml.websso.SingleLogoutProfileImpl"/>
<!-- IDP Discovery Service -->
<bean id="samlIDPDiscovery" class="org.springframework.security.saml.SAMLDiscovery">
<property name="idpSelectionPath" value="/idpSelection.jsp"/>
</bean>
<bean id="metadataDisplayFilter" class="org.springframework.security.saml.metadata.MetadataDisplayFilter" />
<!-- Processing filter for WebSSO Holder-of-Key profile -->
<bean id="samlWebSSOHoKProcessingFilter" class="org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
<property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
</bean>
<bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="successRedirectHandler" />
<property name="authenticationFailureHandler" ref="failureRedirectHandler" />
</bean>
<bean id="successRedirectHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/" />
</bean>
<bean id="failureRedirectHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="useForward" value="true" />
<property name="defaultFailureUrl" value="/error.jsp" />
</bean>
<bean class="org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer">
<property name="sslHostnameVerification" value="default"/>
</bean>
<bean id="samlEntryPoint" class="org.springframework.security.saml.SAMLEntryPoint">
<property name="defaultProfileOptions">
<bean class="org.springframework.security.saml.websso.WebSSOProfileOptions">
<property name="assertionConsumerIndex" value="2" />
<property name="binding" value="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" />
<property name="includeScoping" value="false" />
</bean>
</property>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="samlAuthenticationProvider" />
</security:authentication-manager>
<bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager">
<constructor-arg>
<list>
<bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
<constructor-arg>
<bean class="org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider">
<constructor-arg>
<value type="java.io.File">classpath:IS_metadata.xml</value>
</constructor-arg>
<property name="parserPool" ref="parserPool"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
</bean>
</constructor-arg>
</bean>
</list>
</constructor-arg>
<!--<property name="defaultIDP" value="https://localhost:9443/samlsso" />-->
</bean>
<bean class="org.springframework.security.saml.SAMLBootstrap" />
<bean id="samlAuthenticationProvider" class="org.springframework.security.saml.SAMLAuthenticationProvider" />
<!-- SAML 2.0 WebSSO Assertion Consumer -->
<bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl" />
<!-- SAML 2.0 Web SSO profile -->
<bean id="webSSOprofile" class="org.springframework.security.saml.websso.WebSSOProfileImpl" />
<!-- SAML 2.0 Holder-of-Key WebSSO Assertion Consumer -->
<bean id="hokWebSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl" />
<!-- SAML 2.0 Holder-of-Key Web SSO profile -->
<bean id="hokWebSSOProfile" class="org.springframework.security.saml.websso.WebSSOProfileHoKImpl" />
<!-- SAML 2.0 ECP profile -->
<bean id="ecpprofile" class="org.springframework.security.saml.websso.WebSSOProfileECPImpl"/>
<!-- Provider of default SAML Context -->
<bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl">
<!--<property name="metadataResolver">
<bean class="org.springframework.security.saml.trust.MetadataCredentialResolver">
<constructor-arg index="0" ref="metadata" />
<constructor-arg name="keyManager" index="1" ref="keyManager" />
<property name="useXmlMetadata" value="false" />
</bean>
</property>-->
<property name="storageFactory">
<bean class="org.springframework.security.saml.storage.EmptyStorageFactory" />
</property>
</bean>
<bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger" />
<bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
<constructor-arg value="classpath:samlKeystore.jks" />
<constructor-arg type="java.lang.String" value="nalle123" />
<constructor-arg>
<map>
<entry key="spalias" value="nalle123" />
</map>
</constructor-arg>
<constructor-arg type="java.lang.String" value="spalias" />
</bean>
<!-- Class loading incoming SAML messages from httpRequest stream -->
<bean id="processor" class="org.springframework.security.saml.processor.SAMLProcessorImpl">
<constructor-arg>
<list>
<ref bean="redirectBinding" />
<ref bean="postBinding" />
<ref bean="artifactBinding" />
<ref bean="soapBinding" />
<ref bean="paosBinding" />
</list>
</constructor-arg>
</bean>
<!-- Bindings, encoders and decoders used for creating and parsing messages -->
<bean id="postBinding" class="org.springframework.security.saml.processor.HTTPPostBinding">
<constructor-arg ref="parserPool" />
<constructor-arg ref="velocityEngine" />
</bean>
<bean id="redirectBinding" class="org.springframework.security.saml.processor.HTTPRedirectDeflateBinding">
<constructor-arg ref="parserPool" />
</bean>
<bean id="artifactBinding" class="org.springframework.security.saml.processor.HTTPArtifactBinding">
<constructor-arg ref="parserPool" />
<constructor-arg ref="velocityEngine" />
<constructor-arg>
<bean class="org.springframework.security.saml.websso.ArtifactResolutionProfileImpl">
<constructor-arg>
<bean class="org.apache.commons.httpclient.HttpClient">
<constructor-arg>
<bean class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager" />
</constructor-arg>
</bean>
</constructor-arg>
<property name="processor">
<bean class="org.springframework.security.saml.processor.SAMLProcessorImpl">
<constructor-arg ref="soapBinding" />
</bean>
</property>
</bean>
</constructor-arg>
</bean>
<bean id="soapBinding" class="org.springframework.security.saml.processor.HTTPSOAP11Binding">
<constructor-arg ref="parserPool" />
</bean>
<bean id="paosBinding" class="org.springframework.security.saml.processor.HTTPPAOS11Binding">
<constructor-arg ref="parserPool" />
</bean>
<!-- XML parser pool needed for OpenSAML parsing -->
<bean id="parserPool" class="org.opensaml.xml.parse.StaticBasicParserPool" init-method="initialize">
<property name="builderFeatures">
<map>
<entry key="http://apache.org/xml/features/dom/defer-node-expansion" value="false" />
</map>
</property>
</bean>
<bean id="parserPoolHolder" class="org.springframework.security.saml.parser.ParserPoolHolder" />
<!-- Initialization of the velocity engine -->
<bean id="velocityEngine" class="org.springframework.security.saml.util.VelocityFactory" factory-method="getEngine" />
</beans>
在这种情况下,我使用 WSO2 Identity Server v4.5.0 作为 IdP,这是 WSO2 IdP 元数据:
<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://localhost:9443/samlsso" validUntil="2023-09-23T06:57:15.396Z">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDXTCCAkWgAwIBAgIETYI6hjANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJJ
UjELMAkGA1UECBMCTkExDzANBgNVBAcTBlRlaHJhbjENMAsGA1UEChMEV1NPMjEQ
MA4GA1UECxMHV1NPMiBJUzERMA8GA1UEAxMIaG9zdG5hbWUwHhcNMTUwOTI3MDYy
NDE2WhcNMTUxMjI2MDYyNDE2WjBfMQswCQYDVQQGEwJJUjELMAkGA1UECBMCTkEx
DzANBgNVBAcTBlRlaHJhbjENMAsGA1UEChMEV1NPMjEQMA4GA1UECxMHV1NPMiBJ
UzERMA8GA1UEAxMIaG9zdG5hbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCOD3I08VYC+auzqP6sZbnejD40jtuDuX/IooDxzphysqeTaPVOq0Fquv9i
uIb6XfxGk1hbFiThriCynXAkxeNbmK2ByurRVoJBgdFcCB9JfbGNapVodAbVl9cR
5kXmMJAdqXFDOrMCluira/7HzR0SpoG6A41M/cOkJHq7qtdQlCBaC+L0C5KK+P6/
g4X1zKIt5+vmn1lnDDxdOlCUsv5xVgEYLai+2ArPCZzMxKwlGQ/yWoDky2HXRrnx
ja/vV0J1VeV86tVwyxMb4Bm4XKohrH5sVtzE296JoiPl3rLfGeWpYEO4DXfJYLbi
4+kUvQ4MXTcsSDwQI9aBwVhia8uVAgMBAAGjITAfMB0GA1UdDgQWBBT5CS2/DmR3
lWx35Pmf1jZwbYJpcTANBgkqhkiG9w0BAQsFAAOCAQEANU/dEo7hWpQEDaYvaZmN
IJbck5fqKw4bgbPE2D6ifaYdb4SxxbNL3eBHg1Hbr5hCwLuX4zeqS9D8mrGzWnap
ZgG88VtDl/Y75t9Q3/y8PaRzHKaijo6ydyewLRtumhxFf/nXVKod9kNSPrOorM+o
T/1ht+yfaxCeeK32aV/SLs42raPI3LAT2ZyPimiVhsou72jXD7st8aRhm21qliZq
Qezbz8MccO1peASUikh8ksXNzb6Uh/3o/ks03NJibBZXwgzXR/62KXq8+FRNKEy+
Ec0yudq7bSaBGalQ3SqLXycKP9/Ct58eI2qRVGa0RwqcieDZIspFCgbAlZ2+qIDU
rQ==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://localhost:9443/samlsso"
ResponseLocation="https://localhost:9443/samlsso"/>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://localhost:9443/samlsso"/>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://localhost:9443/samlsso"/>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser"
hoksso:ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
xmlns:hoksso="urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser"
Location="https://localhost:9443/samlsso"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>
谁能帮我配置 Spring Security SAML 以正确启用密钥持有者配置文件?
编辑:
好吧,我的错。由于 IdP 不要求提供客户端证书,因此我正在使用的“WSO2 IS”版本(v4.5.0)似乎不支持发布 SAML HoK 主题确认断言(我相应地更新了问题)。我将尝试使用最新版本的 WSO2 Identity Server (v5.0.0) 并分享结果。
我仍然不确定我的 spring 配置是否正确。
谢谢
【问题讨论】:
【参考方案1】:上述 Spring SAML 配置足以在 SP 端启用 Holder-of-key 配置文件。我用 SimpleSAMLphp 作为 IdP 进行了尝试,它成功了。
WSO2 似乎不支持 HoK 浏览器 SSO 配置文件(如果我错了请纠正我),相反,它支持通过 Security Token Service (STS) 获取/发出 HoK 消息。
【讨论】:
以上是关于如何为密钥持有者配置文件配置 Spring Security SAML 扩展的主要内容,如果未能解决你的问题,请参考以下文章
如何为 Spring Boot 应用程序的 gradle 构建设置活动配置文件?