问题
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