Spring SAML 中的 SSL 对等主机名验证失败

SSL peer failed hostname validation in Spring SAML

我正在尝试将我的 Spring 引导项目配置为对第三方 IDP 使用 SAML 身份验证。我已经实现了使来自 vdenotaris 的配置与 SSOCircle 提供程序一起工作,现在我想将其切换到其他提供程序。

SAML 元数据端点启用了 HTTPS,我已经创建了一个密钥库,其中包含元数据中给出的证书(用于签名和加密)和 HTTP 端点提供的证书(基于 this answer).然后,我将它们保存在证书文件 ($CERTIFICATE_FILE) 中,并使用此脚本生成我的密钥库:

keytool -delete -alias third-party -keystore $KEYSTORE_FILE -storepass $KEYSTORE_PASSWORD
keytool -import -alias third-party -file $CERTIFICATE_FILE -keystore $KEYSTORE_FILE -storepass $KEYSTORE_PASSWORD -noprompt
keytool -genkeypair -alias mycompany -keypass mycompanypass -keystore $KEYSTORE_FILE

然后,当使用此密钥库检索 SAML 元数据时,出现此错误:

org.opensaml.saml2.metadata.provider.MetadataProviderException: org.opensaml.saml2.metadata.provider.MetadataProviderException: Error retrieving metadata from https://third.party.provider/metadata
    at org.opensaml.saml2.metadata.provider.HTTPMetadataProvider.fetchMetadata(HTTPMetadataProvider.java:274)
    at org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider.refresh(AbstractReloadingMetadataProvider.java:267)
    at org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider.doInitialization(AbstractReloadingMetadataProvider.java:236)
    at org.opensaml.saml2.metadata.provider.AbstractMetadataProvider.initialize(AbstractMetadataProvider.java:407)
    at org.springframework.security.saml.metadata.ExtendedMetadataDelegate.initialize(ExtendedMetadataDelegate.java:167)
    at org.springframework.security.saml.metadata.MetadataManager.initializeProvider(MetadataManager.java:412)
    at org.springframework.security.saml.metadata.MetadataManager.refreshMetadata(MetadataManager.java:238)
    at org.springframework.security.saml.metadata.CachingMetadataManager.refreshMetadata(CachingMetadataManager.java:86)
    at org.springframework.security.saml.metadata.MetadataManager$RefreshTask.run(MetadataManager.java:1040)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
Caused by: org.opensaml.saml2.metadata.provider.MetadataProviderException: Error retrieving metadata from https://third.party.provider/metadata
    at org.opensaml.saml2.metadata.provider.HTTPMetadataProvider.fetchMetadata(HTTPMetadataProvider.java:274)
    at org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider.refresh(AbstractReloadingMetadataProvider.java:255)
    ... 9 common frames omitted
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.springframework.security.saml.trust.httpclient.TLSProtocolSocketFactory.createSocket(TLSProtocolSocketFactory.java:97)
    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:323)
    at org.opensaml.saml2.metadata.provider.HTTPMetadataProvider.fetchMetadata(HTTPMetadataProvider.java:250)
    ... 10 common frames omitted

这些是我在 vdenotaris 的链接项目中配置的相关部分:

// Setup TLS Socket Factory
@Bean
public TLSProtocolConfigurer tlsProtocolConfigurer() {
    return new TLSProtocolConfigurer();
}

@Bean
public ProtocolSocketFactory socketFactory() {
    return new TLSProtocolSocketFactory(keyManager(), null, "allowAll");
}

@Bean
public Protocol socketFactoryProtocol() {
    return new Protocol("https", socketFactory(), 443);
}

@Bean
public MethodInvokingFactoryBean socketFactoryInitialization() {
    MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
    methodInvokingFactoryBean.setTargetClass(Protocol.class);
    methodInvokingFactoryBean.setTargetMethod("registerProtocol");
    Object[] args = { "https", socketFactoryProtocol() };
    methodInvokingFactoryBean.setArguments(args);
    return methodInvokingFactoryBean;
}

// Central storage of cryptographic keys
@Bean
public KeyManager keyManager() {
    DefaultResourceLoader loader = new DefaultResourceLoader();
    Resource storeFile = loader.getResource("classpath:/saml/mySamlKeystore.jks");
    String storePass = "storepass";
    Map<String, String> passwords = new HashMap<String, String>();
    passwords.put("mycompany", "mycompanypass");
    String defaultKey = "mycompany";
    return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);
}

然而,在这里我发现了一些误解。据我所知,TLSProtocolConfigurer 本身创建了一个 TLSProtocolSocketFactory,那么为什么示例项目会同时创建两个 bean?根据docs使用TLSProtocolConfigurer应该就够了,但是如何创建socketFactoryProtocol()呢?

如果这里有一些光线,我将不胜感激。

我没有提供导入的证书文件的密码:

@Bean
public KeyManager keyManager() {
    DefaultResourceLoader loader = new DefaultResourceLoader();
    Resource storeFile = loader.getResource("classpath:/saml/mySamlKeystore.jks");
    String storePass = "storepass";
    Map<String, String> passwords = new HashMap<String, String>();
    passwords.put("mycompany", "mycompanypass");
    passwords.put("third-party", "mycompanypass");
    String defaultKey = "mycompany";
    return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);
}

有了这个,应用程序可以从密钥库中读取证书并信任它们,而无需将它们安装在 JDK cacerts 中。

@Bean
    @Qualifier("idp-ssocircle")
    public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider()
        throws MetadataProviderException {
    String idpSSOCircleMetadataURL = "https://idp.ssocircle.com/idp-meta.xml";
    HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(
            this.backgroundTaskTimer, httpClient(), idpSSOCircleMetadataURL);
    httpMetadataProvider.setParserPool(parserPool());
    ExtendedMetadataDelegate extendedMetadataDelegate = 
            new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());
    extendedMetadataDelegate.setMetadataTrustCheck(false);
    extendedMetadataDelegate.setMetadataRequireSignature(false);
    backgroundTaskTimer.purge();
    return extendedMetadataDelegate;
}

extendedMetadataDelegate.setMetadataTrustCheck(假);

这是我的 JKS key-manager bean 配置。

@Bean
public ProtocolSocketFactory socketFactory() {
return new TLSProtocolSocketFactory(keyManager(), null, "default");
}

@Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource("classpath:/saml/samlKeystore.jks");
String storePass = keyStorePwd;
Map<String, String> passwords = new HashMap<String, String>();
passwords.put(keyStoreAlias, keyStorePwd);
String defaultKey = keyStoreAlias;
return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);

}

解决方案:

IDP 似乎更改了他们的 Public 证书,该证书在我们的本地密钥库 (samlKeystore.jks) 中不可用。我使用 OpenSSL 命令手动下载了他们的 public 证书,并使用 keytool 实用程序导入了相同的证书。

使用 OpenSSL 命令获取 public 证书:

openssl s_client -showcerts -connect iam-sso.google.net:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >mycertfile.pem

将其导入密钥库:

keytool -import -alias "new-public-cert" -keystore /usr/share/tomcat8/webapps/ROOT/WEB-INF/classes/saml/samlKeystore.jks

只需删除 TLSProtocolConfigurer bean 并将元数据信任检查设置为 false