简体   繁体   English

Spring Security SAML-如何配置客户端身份验证?

[英]Spring Security SAML - how to configure client auth?

I'm trying to adapt the Spring Security SAML sample application to use a test IDP (provided to me by someone else) instead of ssocircle. 我正在尝试使Spring Security SAML示例应用程序改用测试IDP(由其他人提供)代替ssocircle。 Clicking on the "SAML Login" correctly redirects me to the SSO login page of the IDP but after login and redirection back to the sample app I get an exception (apparently during artifact resolution) at the root of which is: 单击“ SAML登录”正确地将我重定向到IDP的SSO登录页面,但是登录并重定向回到示例应用程序后,我得到了一个异常(显然是在工件解析期间),其根源是:

org.opensaml.ws.message.decoder.MessageDecodingException: Error when sending request to artifact resolution service.
    at org.springframework.security.saml.websso.ArtifactResolutionProfileImpl.getArtifactResponse(ArtifactResolutionProfileImpl.java:110)
    at org.springframework.security.saml.websso.ArtifactResolutionProfileBase.resolveArtifact(ArtifactResolutionProfileBase.java:101)
    ... 34 more
Caused by: javax.net.ssl.SSLPeerUnverifiedException: SSL peer failed hostname validation for name: null
    at org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory.verifyHostname(TLSProtocolSocketFactory.java:233)
    at org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory.createSocket(TLSProtocolSocketFactory.java:186)
    at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707)
    at org.apache.commons.httpclient.MultiThreadedHttpConnectionManager$HttpConnectionAdapter.open(MultiThreadedHttpConnectionManager.java:1361)
    at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:387)
    at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:346)
    at org.springframework.security.saml.websso.ArtifactResolutionProfileImpl.getArtifactResponse(ArtifactResolutionProfileImpl.java:99)

After digging for a while, I realized that the server expects client authentication at the relevant port. 经过一会儿的挖掘,我意识到服务器希望在相关端口进行客户端身份验证。 If I connect to it like this, I get a valid response: 如果我以这种方式连接到它,则会得到一个有效的响应:

curl -k --cert spcert.pem --key spkey.pem https://testidp:8110/idp/profile/SAML2/SOAP/ArtifactResolution

Also, the exception goes away if I disable clientAuth on the IDP by editing server.xml of the IDP tomcat and changing clientAuth to "false" in the relevant <Connector> tag. 另外,如果我通过编辑IDP tomcat的server.xml并在相关的<Connector>标记中将clientAuth更改为“ false”来禁用IDP上的clientAuth,则该异常消失。

Connecting to the IDP's port 8110 works fine if I use apache httpclient like so 如果我像这样使用apache httpclient,则连接到IDP的端口8110可以正常工作

package at.awst.perkele.httpstest;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

public class HTTPSTest {
    private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); // "JKS";
    private static final String CA_KEYSTORE_PATH = "myKeystore.jks";
    private static final String CA_KEYSTORE_PASS = "secret";

    private static final String CLIENT_KEYSTORE_TYPE = KeyStore.getDefaultType(); // "JKS";
    private static final String CLIENT_KEYSTORE_PATH = "myKeystore.jks";
    private static final String CLIENT_KEYSTORE_PASS = "secret";

    private static final String HTTPS_URL = "https://testidp:8110/idp/profile/SAML2/SOAP/ArtifactResolution";

    public static void main(String[] args) throws Exception {
        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(createSslCustomContext(), new String[] { "TLSv1" }, 
                null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());

        try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
            HttpGet req = new HttpGet(HTTPS_URL);
            try (CloseableHttpResponse response = httpclient.execute(req)) {
                HttpEntity entity = response.getEntity();

                System.out.println(String.format("Reponse status: %s", response.getStatusLine()));
                System.out.println(String.format("Response entity: %s", entity.toString()));

                BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent()));
                String line = null;
                while ((line = in.readLine()) != null) {
                    System.out.println(line);
                }
                EntityUtils.consume(entity);
            }
        }
    }

    private static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
        // Trusted CA keystore
        KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
        tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());

        // Client keystore
        KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
        cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());

        SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(tks, new TrustSelfSignedStrategy()) 
                .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray())
                .build();
        return sslcontext;
    }

}

However, I don't know how to configure Spring SAML's TLSProtocolConfigurer correctly (or whatever is needed to use the client key). 但是,我不知道如何正确配置Spring SAML的TLSProtocolConfigurer(或使用客户端密钥所需的一切)。

So, how can I tell Spring Security SAML to use my client key for client authentication in TLS/SSL connections? 那么,如何告诉Spring Security SAML在TLS / SSL连接中使用我的客户端密钥进行客户端身份验证?

OK, figured out how to enable clientAuth in TLS connections for Spring SAML. 好的,了解了如何在Spring SAML的TLS连接中启用clientAuth。 This is my Service Provider config from securityContext.xml: 这是来自securityContext.xml的我的服务提供者配置:

<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:metadata/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="signMetadata" value="true" />
            <property name="signingKey" value="mykey" />
            <property name="encryptionKey" value="mykey" />
            <property name="tlsKey" value="mykey" />
        </bean>
    </constructor-arg>
</bean>  

ClientAuth is enabled by setting the client key via <property name="tlsKey" value="mykey" /> 通过<property name="tlsKey" value="mykey" />设置客户端密钥可以启用ClientAuth

The key has to be declared in the JKSKeyManager as usual: 必须像往常一样在JKSKeyManager中声明密钥:

<bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
    <constructor-arg value="classpath:security/keystore.jks" />
    <constructor-arg type="java.lang.String" value="secret" />
    <constructor-arg>
        <map>
            <entry key="mykey" value="secret" />
        </map>
    </constructor-arg>
    <constructor-arg type="java.lang.String" value="mykey" />
</bean>

This is mentioned in the docs here as "Populate credential used for SSL/TLS client authentication. In case ExtendedMetadata specifies property tlsKey it will be used as an alias to lookup key from keyManager bean. Otherwise no credential will be provided for client authentication." 在本文的文档中被称为“用于SSL / TLS客户端身份验证的填充凭据。如果ExtendedMetadata指定属性tlsKey,它将用作别名,以从keyManager bean查找密钥。否则,将不提供用于身份验证的凭据。” Took me a while to find that ;-) 花了我一段时间才找到;-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM