Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>

Posted

技术标签:

【中文标题】Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>【英文标题】:Spring Security SAML: Getting <Signature> block to appear in <AuthnRequest> 【发布时间】:2016-05-26 22:54:36 【问题描述】:

我很难让 Spring Security SAML 与 ADFS 2.0 一起使用。

根据我目前的配置,生成的AuthnRequest 看起来像这样:-

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO"
    Destination="https://server/adfs/ls/"
    ForceAuthn="false" ID="a14edaf38ih92bi8acji5a1664a80e"
    IsPassive="false" IssueInstant="2016-02-15T21:05:57.980Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
    <saml2p:Scoping ProxyCount="2"/>
</saml2p:AuthnRequest>

但是,它会导致 ADFS 端出现错误:-

Microsoft.IdentityModel.Protocols.XmlSignature.SignatureVerificationFailedException: MSIS0038: SAML Message has wrong signature. Issuer: 'https://localhost:8443/helix/saml/metadata'.
   at Microsoft.IdentityServer.Protocols.Saml.Contract.SamlContractUtility.CreateSamlMessage(MSISSamlBindingMessage message)
   at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.CreateErrorMessage(CreateErrorMessageRequest createErrorMessageRequest)
   at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.ProcessRequest(Message requestMessage)

我的安全团队告诉我,我的 AuthnRequest 应该是这样的:-

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO" Destination="https://server/adfs/ls/" ID="_e082771303738e4e6872e8d5711446d4" IssueInstant="2016-02-15T19:51:50.627Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <Reference URI="#_e082771303738e4e6872e8d5711446d4">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <DigestValue>S8r/XbIhlFGFSMfLoSt/7IlksiI=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>TT4n3==...</SignatureValue>
        <KeyInfo>
            <X509Data>
                <X509Certificate>MIIC8z...</X509Certificate>
            </X509Data>
        </KeyInfo>
    </Signature>
    <saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
    <saml2p:RequestedAuthnContext Comparison="exact">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>

...但是,我在尝试使&lt;Signature&gt; 块出现在我的AuthnRequest 中时遇到了麻烦。

我当前的 Spring Security SAML 配置看起来像这样......我很抱歉它相当冗长,但我不确定除了整个配置之外还要在此处粘贴什么。

@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter 
    private static final String METADATA_URL = "https://server/federationmetadata/2007-06/federationmetadata.xml";
    private static final String ALIAS = "apollo";
    private static final String STORE_PASS = "secret";

    @Autowired
    private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;

    @Autowired
    private SAMLAuthenticationProvider samlAuthenticationProvider;

    @Bean
    public static SAMLBootstrap SAMLBootstrap() 
        return new CustomSamlBootstrap();
    

    @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 MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() 
        return new MultiThreadedHttpConnectionManager();
    

    @Bean
    public HttpClient httpClient(MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager) 
        return new HttpClient(multiThreadedHttpConnectionManager);
    

    @Bean
    public SAMLAuthenticationProvider samlAuthenticationProvider() 
        SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
        samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
        samlAuthenticationProvider.setForcePrincipalAsString(false);
        return samlAuthenticationProvider;
    

    @Bean
    public SAMLContextProviderImpl contextProvider() 
        return new SAMLContextProviderImpl();
    

    @Bean
    public SAMLDefaultLogger samlLogger() 
        return new SAMLDefaultLogger();
    

    @Bean
    public WebSSOProfileConsumer webSSOprofileConsumer() 
        return new WebSSOProfileConsumerImpl();
    

    @Bean
    public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() 
        return new WebSSOProfileConsumerHoKImpl();
    

    @Bean
    public WebSSOProfile webSSOprofile() 
        return new WebSSOProfileImpl();
    

    @Bean
    public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() 
        return new WebSSOProfileConsumerHoKImpl();
    

    @Bean
    public WebSSOProfileECPImpl ecpprofile() 
        return new WebSSOProfileECPImpl();
    

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

    @Bean
    public KeyManager keyManager() 
        DefaultResourceLoader loader = new DefaultResourceLoader();
        Resource storeFile = loader.getResource("classpath:keystore.jks");
        Map<String, String> passwords = new HashMap<>();
        passwords.put(ALIAS, STORE_PASS);
        return new JKSKeyManager(storeFile, STORE_PASS, passwords, ALIAS);
    

    @Bean
    public WebSSOProfileOptions webSSOProfileOptions() 
        return new WebSSOProfileOptions();
    

    @Bean
    public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) 
        SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
        samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
        return samlEntryPoint;
    

    @Bean
    public ExtendedMetadata extendedMetadata() 
        ExtendedMetadata extendedMetadata = new ExtendedMetadata();
        extendedMetadata.setIdpDiscoveryEnabled(false);

        // #######
        // ####### In theory, by setting these keys, the signature block should appear,
        // ####### but, that didn't work for me
        // #######
        extendedMetadata.setSignMetadata(true);
        extendedMetadata.setSigningKey(ALIAS);
        extendedMetadata.setEncryptionKey(ALIAS);
        // #######

        return extendedMetadata;
    

    @Bean
    public ExtendedMetadataDelegate extendedMetadataDelegate(HttpClient httpClient,
                                                             ParserPool parserPool,
                                                             ExtendedMetadata extendedMetadata) throws MetadataProviderException 
        Timer backgroundTaskTimer = new Timer(true);
        HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(backgroundTaskTimer,
                                                                             httpClient,
                                                                             METADATA_URL);
        httpMetadataProvider.setParserPool(parserPool);
        ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider,
                                                                                         extendedMetadata);
        extendedMetadataDelegate.setMetadataTrustCheck(false);

        return extendedMetadataDelegate;
    

    @Bean
    public CachingMetadataManager metadata(ExtendedMetadataDelegate extendedMetadataDelegate) throws MetadataProviderException 
        List<MetadataProvider> providers = new ArrayList<>();
        providers.add(extendedMetadataDelegate);
        return new CachingMetadataManager(providers);
    

    @Bean
    public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) 
        MetadataGenerator metadataGenerator = new MetadataGenerator();

        metadataGenerator.setExtendedMetadata(extendedMetadata);
        metadataGenerator.setIncludeDiscoveryExtension(false);
        metadataGenerator.setKeyManager(keyManager);

        return metadataGenerator;
    

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

    @Bean
    public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() 
        SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successRedirectHandler.setDefaultTargetUrl("/landing");
        return successRedirectHandler;
    

    @Bean
    public SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() 
        SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
        failureHandler.setUseForward(true);
        failureHandler.setDefaultFailureUrl("/error");
        return failureHandler;
    

    @Bean
    public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter(SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
                                                                       AuthenticationManager authenticationManager,
                                                                       SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception 
        SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
        samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
        samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager);
        samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
        return samlWebSSOHoKProcessingFilter;
    

    @Bean
    public SAMLProcessingFilter samlProcessingFilter(AuthenticationManager authenticationManager,
                                                     SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
                                                     SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception 
        SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
        samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager);
        samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
        samlWebSSOProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
        return samlWebSSOProcessingFilter;
    

    @Bean
    public MetadataGeneratorFilter metadataGeneratorFilter(MetadataGenerator metadataGenerator) 
        return new MetadataGeneratorFilter(metadataGenerator);
    

    @Bean
    public SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler() 
        SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
        successLogoutHandler.setDefaultTargetUrl("/");
        return successLogoutHandler;
    

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

    @Bean
    public SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
                                                                 SecurityContextLogoutHandler securityContextLogoutHandler) 
        return new SAMLLogoutProcessingFilter(simpleUrlLogoutSuccessHandler, securityContextLogoutHandler);
    

    @Bean
    public SAMLLogoutFilter samlLogoutFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
                                             SecurityContextLogoutHandler securityContextLogoutHandler) 
        return new SAMLLogoutFilter(simpleUrlLogoutSuccessHandler,
                                    new LogoutHandler[]securityContextLogoutHandler,
                                    new LogoutHandler[]securityContextLogoutHandler);
    

    @Bean
    public HTTPArtifactBinding artifactBinding(HTTPSOAP11Binding httpSOAP11Binding,
                                               HttpClient httpClient,
                                               ParserPool parserPool,
                                               VelocityEngine velocityEngine) 
        ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient);
        artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(httpSOAP11Binding));

        return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile);
    

    @Bean
    public HTTPSOAP11Binding httpSOAP11Binding(ParserPool parserPool) 
        return new HTTPSOAP11Binding(parserPool);
    

    @Bean
    public HTTPPostBinding httpPostBinding(ParserPool parserPool, VelocityEngine velocityEngine) 
        return new HTTPPostBinding(parserPool, velocityEngine);
    

    @Bean
    public HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) 
        return new HTTPRedirectDeflateBinding(parserPool);
    

    @Bean
    public HTTPPAOS11Binding httpPAOS11Binding(ParserPool parserPool) 
        return new HTTPPAOS11Binding(parserPool);
    

    @Bean
    public SAMLProcessorImpl processor(HTTPRedirectDeflateBinding httpRedirectDeflateBinding,
                                       HTTPPostBinding httpPostBinding,
                                       HTTPArtifactBinding httpArtifactBinding,
                                       HTTPSOAP11Binding httpSOAP11Binding,
                                       HTTPPAOS11Binding httpPAOS11Binding) 
        Collection<SAMLBinding> bindings = new ArrayList<>();
        bindings.add(httpRedirectDeflateBinding);
        bindings.add(httpPostBinding);
        bindings.add(httpArtifactBinding);
        bindings.add(httpSOAP11Binding);
        bindings.add(httpPAOS11Binding);
        return new SAMLProcessorImpl(bindings);
    

    @Bean
    public FilterChainProxy filterChainProxy(SAMLEntryPoint samlEntryPoint,
                                             SAMLLogoutFilter samlLogoutFilter,
                                             MetadataDisplayFilter metadataDisplayFilter,
                                             SAMLProcessingFilter samlProcessingFilter,
                                             SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter,
                                             SAMLLogoutProcessingFilter samlLogoutProcessingFilter) throws Exception 
        List<SecurityFilterChain> chains = new ArrayList<>();
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint));
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter));
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
                                                  metadataDisplayFilter));
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlProcessingFilter));
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
                                                  samlWebSSOHoKProcessingFilter));
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
                                                  samlLogoutProcessingFilter));

        return new FilterChainProxy(chains);
    

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

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

如果有人能将我推向正确的方向,我将不胜感激。

谢谢。

更新

MetadataGenerator 生成的我的 SP 元数据如下所示:-

<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="https___localhost_8443_helix_saml_metadata" entityID="https://localhost:8443/helix/saml/metadata">
    <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor use="signing">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>MIICx....</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:KeyDescriptor use="encryption">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>MIICxz....</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SingleLogout"/>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:8443/helix/saml/SingleLogout"/>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SSO" index="0" isDefault="true"/>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://localhost:8443/helix/saml/SSO" index="1"/>
    </md:SPSSODescriptor>
</md:EntityDescriptor>

我还想指出,我使用的是 SHA256withRSA 而不是 SHA1withRSA。因此,我将默认的 SAMLBootstrap bean 替换为以下内容:-

public final class CustomSamlBootstrap extends SAMLBootstrap 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
        super.postProcessBeanFactory(beanFactory);
        BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
        config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
        config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
    

更新 2

我通过修改 WebSSOProfileOptions 让它工作了。

@Bean
public WebSSOProfileOptions webSSOProfileOptions() 
    WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
    webSSOProfileOptions.setIncludeScoping(false);

    // Added this line
    webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);

    return webSSOProfileOptions;

【问题讨论】:

【参考方案1】:

您使用什么绑定来向 IDP 发送请求?如果是 HTTP 请求,SAML 标准要求在传递消息之前删除他的签名。然后对序列化的请求执行签名并作为 GET 参数发送。

【讨论】:

感谢您的回复。如何验证/检查我正在使用的绑定?我以为我一直在通过 HTTP-Post 发送,但我认为我的绑定是错误的,因为我的安全团队告诉我 ADFS 正在以查询参数字符串的形式接收我的响应,这意味着我使用的是 GET 而不是 POST。 对于我的WebSSOProfileOptions,绑定设置为urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST。当设置为较早或urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect 时,AuthnRequest 中的ProtocolBinding 始终显示为urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST 我通过添加生成的 SP 元数据进行了更新,并且我使用了 SHA256withRSA 而不是 SHA1withRSA。谢谢。【参考方案2】:

我相信你应该设置:

@Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) 
    MetadataGenerator metadataGenerator = new MetadataGenerator();
    // ... other configuration here
    metadataGenerator.setRequestSigned ( true );

    return metadataGenerator;

在 ExtendedMetadata 中,您只是设置要签名的元数据文件。这不会影响 AuthnRequest 的签名。

【讨论】:

以上是关于Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>

生成 SP 元数据时出现意外的堆栈跟踪表单 Spring-Security-SAML?

Spring Security SAML2 未找到 entityID

未抛出 Spring Security SAML DisabledException

没有 Spring Boot 的 Spring Security SAML 身份元数据

如何使用 spring-security-saml2 配置服务提供者以使用 EncryptedAssertions?