0

按照参考文档:

https://docs.spring.io/spring-security/site/docs/5.5.2/reference/html5/#servlet-saml2login-opensaml-customization

和示例项目:

https://github.com/spring-projects/spring-security-samples/tree/5.5.x/servlet/spring-boot/java/saml2-login

我已经实现了与 Okta 身份提供者集成的一个小原型,这让我可以选择签署 AuthnRequest。生成 SAMLRequest 时一切正常:

<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://localhost:8080/login/saml2/sso/okta" Destination="https://goolex.okta.com/app/goolex_a1_1/eXf1hi6xl7JFdgU7A696/sso/saml" ID="ARQ949414e-2bd3-4571-9508-db396f0a3c94" IssueInstant="2021-09-10T06:50:44.982Z" 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">http://localhost:8080/saml2/service-provider-metadata/okta</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        
        <ds:SignedInfo>
            
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            
            <ds:Reference URI="#ARQ949414e-2bd3-4571-9508-db396f0a3c94">
                
                <ds:Transforms>
                    
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    
                </ds:Transforms>
                
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                
                <ds:DigestValue>9KN16v4zPb93VXdpWQCsuH7ihPh4PJ8FGNFgfQ4lBUg=</ds:DigestValue>
                
            </ds:Reference>
            
        </ds:SignedInfo>
        
        <ds:SignatureValue>
g8v...&#13;
0ul...&#13;
ASY...&#13;
BBN...&#13;
87g...A==
</ds:SignatureValue>
        
    </ds:Signature>
</saml2p:AuthnRequest>

Spring Security SAML 模块使用最新的 OpenSAML 库实现,这将使我能够在 AuthnRequest 实例被序列化之前对其进行后处理。然后我定义了一个自定义的 AuthnRequestMarshaller,如文档中所指定,用于配置一些参数:

static {
    OpenSamlInitializationService.requireInitialize(factory -> {
        AuthnRequestMarshaller marshaller = new AuthnRequestMarshaller() {
            @Override
            public Element marshall(XMLObject object, Element element) throws MarshallingException {
                configureAuthnRequest((AuthnRequest) object);
                return super.marshall(object, element);
            }

            @Override
            public Element marshall(XMLObject object, Document document) throws MarshallingException {
                configureAuthnRequest((AuthnRequest) object);
                return super.marshall(object, document);
            }

            private void configureAuthnRequest(AuthnRequest authnRequest) {
                authnRequest.setForceAuthN(true);
            }
        }

        factory.getMarshallerFactory().registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller);
    });
}

即使在这种情况下,一切正常。当我在 AuthnRequest 中定义一个新元素(NameIDPolicy)时出现一个奇怪的错误:

static {
    OpenSamlInitializationService.requireInitialize(factory -> {
        AuthnRequestMarshaller marshaller = new AuthnRequestMarshaller() {
            @Override
            public Element marshall(XMLObject object, Element element) throws MarshallingException {
                configureAuthnRequest((AuthnRequest) object);
                return super.marshall(object, element);
            }

            @Override
            public Element marshall(XMLObject object, Document document) throws MarshallingException {
                configureAuthnRequest((AuthnRequest) object);
                return super.marshall(object, document);
            }

            private void configureAuthnRequest(AuthnRequest authnRequest) {
                authnRequest.setForceAuthN(true);

                NameIDPolicyBuilder nameIDPolicyBuilder = new NameIDPolicyBuilder();
                NameIDPolicy nameIDPolicy = nameIDPolicyBuilder.buildObject();
                nameIDPolicy.setFormat(TRANSIENT);

                authnRequest.setNameIDPolicy(nameIDPolicy);
            }
        }

        factory.getMarshallerFactory().registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller);
    });
}

generetad AuthnRequest 具有新的 xml 元素 (NameIDPolicy),但签名和摘要值已消失:

<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://localhost:8080/login/saml2/sso/okta" AttributeConsumingServiceIndex="0" Destination="https://goolex.okta.com/app/goolex_a1_1/eXf1hi6xl7JFdgU7A696/sso/saml" ForceAuthn="true" ID="ARQ4784e2f-689d-46b2-8635-b416d1c9f37d" IssueInstant="2021-09-10T07:41:31.605Z" 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" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" NameQualifier="http://localhost:8080">http://localhost:8080/saml2/service-provider-metadata/okta</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        
        <ds:SignedInfo>
            
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            
            <ds:Reference URI="#ARQ4784e2f-689d-46b2-8635-b416d1c9f37d">
                
                <ds:Transforms>
                    
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    
                </ds:Transforms>
                
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                
                <ds:DigestValue/>
                
            </ds:Reference>
            
        </ds:SignedInfo>
        
        <ds:SignatureValue/>
        
    </ds:Signature>
    <saml2p:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</saml2p:AuthnRequest>

可能发生这种情况是因为我修改了已签名的原始元素,但我想知道在定义(编组)对象的所有操作之后是否不应该使用自定义编组器 AuthnRequest 签名过程?

可能,一旦定义了 AuthnRequest 对象的所有元素,是否有办法生成新的 (XML) 签名?

提前致谢。

更新:

我尝试使用以下代码手动添加签名:

...
X509Certificate certificate = (X509Certificate) ks.getCertificate(ksAlias);
RSAPrivateKey key = (RSAPrivateKey) ks.getKey(ksAlias, ksPassword.toCharArray());
BasicX509Credential credential = new BasicX509Credential(certificate, key);

SignatureBuilder signatureBuilder = new SignatureBuilder();
SignatureImpl signature = signatureBuilder.buildObject();
signature.setSigningCredential(credential);
signature.setSignatureAlgorithm(authnRequest.getSignature().getSignatureAlgorithm());
signature.setCanonicalizationAlgorithm(authnRequest.getSignature().getCanonicalizationAlgorithm());

authnRequest.setSignature(signature);

Signer.signObject(signature);
...

签名出现在我的 XML 日志中:

 <saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://localhost:8080/login/saml2/sso/okta" AttributeConsumingServiceIndex="0" Destination="https://teamsystem1.okta.com/app/teamsystem1_a1_1/exk1hi6xl2jFdgu7A696/sso/saml" ForceAuthn="true" ID="ARQ59bf12d-6eae-4717-b381-265b93640f01" IssueInstant="2021-09-13T10:25:14.275Z" 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" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" NameQualifier="http://localhost:8080">http://localhost:8080/saml2/service-provider-metadata/okta</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        
        <ds:SignedInfo>
            
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            
            <ds:Reference URI="#ARQ59bf12d-6eae-4717-b381-265b93640f01">
                
                <ds:Transforms>
                    
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    
                </ds:Transforms>
                
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                
                <ds:DigestValue>75AOsLG6mhctNqFhNnTHj8l9mob2gd68XkM14IhZU7k=</ds:DigestValue>
                
            </ds:Reference>
            
        </ds:SignedInfo>
        
        <ds:SignatureValue>
Z3J...&#13;
8pE...&#13;
oRH...&#13;
Zh/...&#13;
Pvc...g==
</ds:SignatureValue>
        
    </ds:Signature>
    <saml2p:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</saml2p:AuthnRequest>

但现在我从 Spring/OpenSAML 签名计算中收到了这个异常:

org.springframework.security.saml2.Saml2Exception: org.opensaml.xmlsec.signature.support.SignatureException: XMLObject does not have XMLSignature instance, unable to compute signature
    at org.springframework.security.saml2.provider.service.authentication.OpenSamlSigningUtils.sign(OpenSamlSigningUtils.java:84) ~[spring-security-saml2-service-provider-5.5.2.jar:5.5.2]
    at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationRequestFactory.createPostAuthenticationRequest(OpenSaml4AuthenticationRequestFactory.java:99) ~[spring-security-saml2-service-provider-5.5.2.jar:5.5.2]
    at org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter.sendPost(Saml2WebSsoAuthenticationRequestFilter.java:199) ~[spring-security-saml2-service-provider-5.5.2.jar:5.5.2]
    at org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter.doFilterInternal(Saml2WebSsoAuthenticationRequestFilter.java:171) ~[spring-security-saml2-service-provider-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.9.jar:5.3.9]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.9.jar:5.3.9]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.9.jar:5.3.9]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
    at java.base/java.lang.Thread.run(Thread.java:836) ~[na:na]
Caused by: org.opensaml.xmlsec.signature.support.SignatureException: XMLObject does not have XMLSignature instance, unable to compute signature
    at org.opensaml.xmlsec.signature.support.impl.provider.ApacheSantuarioSignerProviderImpl.signObject(ApacheSantuarioSignerProviderImpl.java:55) ~[opensaml-xmlsec-impl-4.1.1.jar:na]
    at org.opensaml.xmlsec.signature.support.Signer.signObject(Signer.java:73) ~[opensaml-xmlsec-api-4.1.1.jar:na]
    at org.opensaml.xmlsec.signature.support.SignatureSupport.signObject(SignatureSupport.java:297) ~[opensaml-xmlsec-api-4.1.1.jar:na]
    at org.springframework.security.saml2.provider.service.authentication.OpenSamlSigningUtils.sign(OpenSamlSigningUtils.java:80) ~[spring-security-saml2-service-provider-5.5.2.jar:5.5.2]
    ... 55 common frames omitted

:-(

4

0 回答 0