Spring-SAML 的 SSL 配置问题

Posted

技术标签:

【中文标题】Spring-SAML 的 SSL 配置问题【英文标题】:SSL configuration issue with Spring-SAML 【发布时间】:2014-09-02 03:34:29 【问题描述】:

我正在尝试基于“spring-security-saml2-sample”设置 SP,但是当我在 Tomcat 上部署 WAR 文件时出现以下异常:

Initialization of metadata provider org.opensaml.saml2.metadata.provider.HTTPMetadataProvider@443c35d3 failed, provider will be ignored
org.opensaml.saml2.metadata.provider.MetadataProviderException: org.opensaml.saml2.metadata.provider.MetadataProviderException: Error retrieving metadata from   
https://dominio.com/fed/idp/metadata
        at org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider.refresh(AbstractReloadingMetadataProvider.java:266)
        at org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider.doInitialization(AbstractReloadingMetadataProvider.java:236)
        at org.opensaml.saml2.metadata.provider.AbstractMetadataProvider.initialize(AbstractMetadataProvider.java:407)
        at org.springframework.security.saml.metadata.ExtendedMetadataDelegate.initialize(ExtendedMetadataDelegate.java:167)
        at org.springframework.security.saml.metadata.MetadataManager.initializeProvider(MetadataManager.java:397)
        at org.springframework.security.saml.metadata.MetadataManager.refreshMetadata(MetadataManager.java:245)
        at org.springframework.security.saml.metadata.CachingMetadataManager.refreshMetadata(CachingMetadataManager.java:86)
        at org.springframework.security.saml.metadata.MetadataManager.afterPropertiesSet(MetadataManager.java:141)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:876)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:818)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:735)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:478)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:609)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:469)
        at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:383)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:283)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4210)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4709)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:799)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:779)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:583)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:943)
        at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:778)
        at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:504)
        at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1317)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:324)

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
        at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
        at sun.security.validator.Validator.validate(Unknown Source)
        at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
        ... 77 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
        at java.security.cert.CertPathBuilder.build(Unknown Source)
        ... 83 more

我了解该异常是由于 IdP (Oracle OIF) 自签名证书未正确导入密钥库而引起的。联合 URL 受 SSL 保护,因此我已将 SSL 证书包含在密钥库 (samlKeystore.jks) 中。我还包含了用于签署 SAML 断言响应的 OIF 证书。

这是我正在尝试的 securityContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:security="http://www.springframework.org/schema/security"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
          http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<!-- Enable auto-wiring -->
<context:annotation-config/>
<context:component-scan base-package="org.springframework.security.saml"/>

<!-- Unsecured pages -->
<security:http security="none" pattern="/saml/web/**"/>
<security:http security="none" pattern="/logout.jsp"/>
<security:http security="none" pattern="/favicon.ico"/>

<!-- Secured pages -->
<security:http entry-point-ref="samlEntryPoint">
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</security:http>

<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/logout/**" filters="samlLogoutFilter"/>
        <security:filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
        <security:filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
        <security:filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
        <security:filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
        <security:filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
    </security:filter-chain-map>
</bean>

<!-- Handler deciding where to redirect user after successful login -->
<bean id="successRedirectHandler"
      class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <property name="defaultTargetUrl" value="/"/>
</bean>
<!--
Use the following for interpreting RelayState coming from unsolicited response as redirect URL:
<bean id="successRedirectHandler" class="org.springframework.security.saml.SAMLRelayStateSuccessHandler">
   <property name="defaultTargetUrl" value="/" />
</bean>
-->

<!-- Handler for successful logout -->
<bean id="successLogoutHandler" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
    <property name="defaultTargetUrl" value="/logout.jsp"/>
</bean>

<!-- Register authentication manager with SAML provider -->
<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider ref="samlAuthenticationProvider"/>
</security:authentication-manager>

<!-- Logger for SAML messages and events -->
<bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger"/>

<!-- Central storage of cryptographic keys -->
<bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
    <constructor-arg value="classpath:security/samlKeystore.jks"/>
    <constructor-arg type="java.lang.String" value="nalle123"/>
    <constructor-arg>
        <map>
            <entry key="apollo" value="nalle123"/>
        </map>
    </constructor-arg>
    <constructor-arg type="java.lang.String" value="apollo"/>
</bean>

<!-- Entry point to initialize authentication, default values taken from properties file -->
<bean id="samlEntryPoint" class="org.springframework.security.saml.SAMLEntryPoint">
    <property name="defaultProfileOptions">
        <bean class="org.springframework.security.saml.websso.WebSSOProfileOptions">
            <property name="includeScoping" value="false"/>
        </bean>
    </property>
</bean>

<!-- IDP Discovery Service -->
<bean id="samlIDPDiscovery" class="org.springframework.security.saml.SAMLDiscovery">
    <property name="idpSelectionPath" value="/WEB-INF/security/idpSelection.jsp"/>
</bean>

<!-- 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="entityId" value="urn:gnf:saml"/>
                <property name="signMetadata" value="false"/>
                <property name="bindingsSSO" >
                <list>
                    <value>POST</value>
                </list>
            </property>
        </bean>
    </constructor-arg>
</bean>

<!-- The filter is waiting for connections on URL suffixed with filterSuffix and presents SP metadata there -->
<bean id="metadataDisplayFilter" class="org.springframework.security.saml.metadata.MetadataDisplayFilter"/>

<!-- IDP Metadata configuration - paths to metadata of IDPs in circle of trust is here -->
<!-- Do no forget to call iniitalize method on providers -->
<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:security/idp.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>
            -->
            <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:security/sp.xml</value>
                    </constructor-arg>
                    <property name="parserPool" ref="parserPool"/>
                </bean>
                </constructor-arg>
                <constructor-arg>
                <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                    <property name="local" value="true"/>
                    <property name="alias" value="urn:gnf:saml"/>
                    <property name="securityProfile" value="metaiop"/>
                    <property name="sslSecurityProfile" value="pkix"/>
                    <property name="signingKey" value="apollo"/>
                    <property name="encryptionKey" value="apollo"/>
                    <property name="requireArtifactResolveSigned" value="false"/>
                    <property name="requireLogoutRequestSigned" value="false"/>
                    <property name="requireLogoutResponseSigned" value="false"/>
                    <property name="idpDiscoveryEnabled" value="false"/>
                </bean>
            </constructor-arg>
            </bean>

            <bean class="org.opensaml.saml2.metadata.provider.HTTPMetadataProvider">
                <!-- URL containing the metadata -->
                <constructor-arg>
                    <value type="java.lang.String">https://dominio.com/fed/idp/metadata</value>
                </constructor-arg>
                <!-- Timeout for metadata loading in ms -->
                <constructor-arg>
                    <value type="int">5000</value>
                </constructor-arg>
                <property name="parserPool" ref="parserPool"/>
            </bean>
        </list>
    </constructor-arg>
    <!-- OPTIONAL used when one of the metadata files contains information about this service provider -->
    <property name="hostedSPName" value="urn:gnf:saml"/>
    <!-- OPTIONAL property: can tell the system which IDP should be used for authenticating user by default. -->
    <property name="defaultIDP" value="https://dominio.com/fed/idp"/>
</bean>

<!-- SAML Authentication Provider responsible for validating of received SAML messages -->
<bean id="samlAuthenticationProvider" class="org.springframework.security.saml.SAMLAuthenticationProvider">
    <!-- OPTIONAL property: can be used to store/load user data after login -->
    <!--
    <property name="userDetails" ref="bean" />
    -->
</bean>

<!-- Provider of default SAML Context -->
<bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl"/>

<!-- Processing filter for WebSSO profile messages -->
<bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
</bean>

<!-- 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"/>
</bean>

<!-- Logout handler terminating local session -->
<bean id="logoutHandler"
      class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
    <property name="invalidateHttpSession" value="false"/>
</bean>

<!-- Override default logout processing filter with the one processing SAML messages -->
<bean id="samlLogoutFilter" class="org.springframework.security.saml.SAMLLogoutFilter">
    <constructor-arg ref="successLogoutHandler"/>
    <constructor-arg ref="logoutHandler"/>
    <constructor-arg ref="logoutHandler"/>
</bean>

<!-- 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 ref="successLogoutHandler"/>
    <constructor-arg ref="logoutHandler"/>
</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>

<!-- SAML 2.0 WebSSO Assertion Consumer -->
<bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl"/>

<!-- SAML 2.0 Holder-of-Key WebSSO Assertion Consumer -->
<bean id="hokWebSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>

<!-- SAML 2.0 Web SSO profile -->
<bean id="webSSOprofile" class="org.springframework.security.saml.websso.WebSSOProfileImpl"/>

<!-- SAML 2.0 Holder-of-Key Web SSO profile -->
<bean id="hokWebSSOProfile" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>

<!-- SAML 2.0 ECP profile -->
<bean id="ecpprofile" class="org.springframework.security.saml.websso.WebSSOProfileECPImpl"/>

<!-- SAML 2.0 Logout Profile -->
<bean id="logoutprofile" class="org.springframework.security.saml.websso.SingleLogoutProfileImpl"/>

<!-- 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>
            <property name="processor">
                <bean id="soapProcessor" 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>

<!-- Initialization of OpenSAML library-->
<bean class="org.springframework.security.saml.SAMLBootstrap"/>

<!-- Initialization of the velocity engine -->
<bean id="velocityEngine" class="org.springframework.security.saml.util.VelocityFactory" factory-method="getEngine"/>

<!-- XML parser pool needed for OpenSAML parsing -->
<bean id="parserPool" class="org.opensaml.xml.parse.StaticBasicParserPool" scope="singleton" init-method="initialize"/>
<bean id="parserPoolHolder" class="org.springframework.security.saml.parser.ParserPoolHolder" scope="singleton"/>

我想 IdP 元数据服务的 SSL 证书必须添加到 samlKeystore.jks,对吗?处理 SSL 是否需要任何额外的配置?

非常感谢您,

丹尼尔。

【问题讨论】:

您是否考虑过将该证书导入您的 JRE 信任库或您在应用程序中默认使用的信任库(请参阅this question 或 this one),如果这在您的环境中不合适的话?您可能还对this 感兴趣。 【参考方案1】:

默认情况下,Spring SAML 不使用samlKeystore.jks 来验证使用HTTPMetadataProvider 完成的调用。这意味着您需要将 HTTPS 端点 https://dominio.com/fed/idp/metadata 的证书导入您的 JDK(通常是 jre/lib/security/cacerts)。

还有另一种选择。我刚刚推送了一个提交,通过包含以下 bean,您也可以将 samlKeystore.jks 用于这些调用:

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass" value="org.apache.commons.httpclient.protocol.Protocol"/>
    <property name="targetMethod" value="registerProtocol"/>
    <property name="arguments">
        <list>
            <value>https</value>
            <bean class="org.apache.commons.httpclient.protocol.Protocol">
                <constructor-arg value="https"/>
                <constructor-arg>
                    <bean class="org.springframework.security.saml.trust.httpclient.TLSProtocolSocketFactory"/>
                </constructor-arg>
                <constructor-arg value="443"/>
            </bean>
        </list>
    </property>
</bean>

您需要更新到最新的trunk 才能使用TLSProtocolSocketFactory

【讨论】:

嗨@Vladimir Schafer,您能否建议在 idp.ssocircle.com/idp-meta.xml</value> 当使用 SSL 通过基于 HTTP 的元数据提供程序读取 idp 元数据时【参考方案2】:

您必须将您的 IDP(https://dominio.com/fed/idp/metadata) 证书作为信任导入您的 SP 机器 JDK 路径。通常在&lt;JAVA_HOME&gt;/jre/lib/security/cacerts

【讨论】:

以上是关于Spring-SAML 的 SSL 配置问题的主要内容,如果未能解决你的问题,请参考以下文章

使用 spring-SAML 在 Pentaho 中多租户 SSO 登录后重定向

Tomcat从安装到配置Https SSL证书

ADFS 拒绝 spring-saml 元数据

Spring-SAML:传入的 SAML 消息无效

iis配置ssl,求高手。。

宝塔配置完SSL之后访问显示403 forbidden问题解决