Spring 安全 SAML - 如何配置客户端身份验证?
Spring Security SAML - how to configure client auth?
我正在尝试调整 Spring Security SAML sample application 以使用测试 IDP(由其他人提供给我)而不是 ssocircle。单击 "SAML Login" 正确地将我重定向到 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)
经过一段时间的挖掘,我意识到服务器需要在相关端口进行客户端身份验证。如果我像这样连接到它,我会得到一个有效的响应:
curl -k --cert spcert.pem --key spkey.pem https://testidp:8110/idp/profile/SAML2/SOAP/ArtifactResolution
此外,如果我通过编辑 IDP tomcat 的 server.xml 并在相关 <Connector>
中将 clientAuth 更改为 "false" 来禁用 IDP 上的 clientAuth,则异常消失标签。
如果我像这样使用 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;
}
}
但是,我不知道如何正确配置 Spring SAML 的 TLSProtocolConfigurer(或使用客户端密钥所需的任何内容)。
那么,我如何告诉 Spring 安全 SAML 在 TLS/SSL 连接中使用我的客户端密钥进行客户端身份验证?
好的,了解如何在 Spring SAML 的 TLS 连接中启用 clientAuth。这是我来自 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>
通过 <property name="tlsKey" value="mykey" />
设置客户端密钥启用 ClientAuth
密钥必须像往常一样在 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>
这是 mentioned in the docs here 作为 "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."
我花了一段时间才找到那个 ;-)
我正在尝试调整 Spring Security SAML sample application 以使用测试 IDP(由其他人提供给我)而不是 ssocircle。单击 "SAML Login" 正确地将我重定向到 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)
经过一段时间的挖掘,我意识到服务器需要在相关端口进行客户端身份验证。如果我像这样连接到它,我会得到一个有效的响应:
curl -k --cert spcert.pem --key spkey.pem https://testidp:8110/idp/profile/SAML2/SOAP/ArtifactResolution
此外,如果我通过编辑 IDP tomcat 的 server.xml 并在相关 <Connector>
中将 clientAuth 更改为 "false" 来禁用 IDP 上的 clientAuth,则异常消失标签。
如果我像这样使用 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;
}
}
但是,我不知道如何正确配置 Spring SAML 的 TLSProtocolConfigurer(或使用客户端密钥所需的任何内容)。
那么,我如何告诉 Spring 安全 SAML 在 TLS/SSL 连接中使用我的客户端密钥进行客户端身份验证?
好的,了解如何在 Spring SAML 的 TLS 连接中启用 clientAuth。这是我来自 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>
通过 <property name="tlsKey" value="mykey" />
密钥必须像往常一样在 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>
这是 mentioned in the docs here 作为 "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." 我花了一段时间才找到那个 ;-)