Spring Security SAML - how to configure client auth?

半腔热情 提交于 2020-01-16 15:46:34

问题


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. 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:

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.

Connecting to the IDP's port 8110 works fine if I use apache httpclient like so

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).

So, how can I tell Spring Security SAML to use my client key for client authentication in TLS/SSL connections?


回答1:


OK, figured out how to enable clientAuth in TLS connections for Spring SAML. This is my Service Provider config from 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" />

The key has to be declared in the JKSKeyManager as usual:

<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." Took me a while to find that ;-)



来源:https://stackoverflow.com/questions/35045669/spring-security-saml-how-to-configure-client-auth

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!