Spring 安全 SAML:让 <Signature> 块出现在 <AuthnRequest> 中
Spring Security SAML: Getting <Signature> block to appear in <AuthnRequest>
我很难让 Spring 安全 SAML 与 ADFS 2.0 一起工作。
根据我当前的配置,生成的 AuthnRequest
如下所示:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO"
Destination="https://server/adfs/ls/"
ForceAuthn="false" ID="a14edaf38ih92bi8acji5a1664a80e"
IsPassive="false" IssueInstant="2016-02-15T21:05:57.980Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<saml2p:Scoping ProxyCount="2"/>
</saml2p:AuthnRequest>
但是,它会导致 ADFS 端出错:-
Microsoft.IdentityModel.Protocols.XmlSignature.SignatureVerificationFailedException: MSIS0038: SAML Message has wrong signature. Issuer: 'https://localhost:8443/helix/saml/metadata'.
at Microsoft.IdentityServer.Protocols.Saml.Contract.SamlContractUtility.CreateSamlMessage(MSISSamlBindingMessage message)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.CreateErrorMessage(CreateErrorMessageRequest createErrorMessageRequest)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.ProcessRequest(Message requestMessage)
我的安全团队告诉我,我的 AuthnRequest
应该看起来像这样:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO" Destination="https://server/adfs/ls/" ID="_e082771303738e4e6872e8d5711446d4" IssueInstant="2016-02-15T19:51:50.627Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#_e082771303738e4e6872e8d5711446d4">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>S8r/XbIhlFGFSMfLoSt/7IlksiI=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>TT4n3==...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIC8z...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<saml2p:RequestedAuthnContext Comparison="exact">
<saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
...但是,我无法尝试让 <Signature>
块出现在我的 AuthnRequest
.
中
我当前的 Spring 安全 SAML 配置如下所示...我很抱歉它相当冗长,但我不确定除了整个配置之外还应该在此处粘贴什么。
@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {
private static final String METADATA_URL = "https://server/federationmetadata/2007-06/federationmetadata.xml";
private static final String ALIAS = "apollo";
private static final String STORE_PASS = "secret";
@Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
@Autowired
private SAMLAuthenticationProvider samlAuthenticationProvider;
@Bean
public static SAMLBootstrap SAMLBootstrap() {
return new CustomSamlBootstrap();
}
@Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
@Bean
public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
return new MultiThreadedHttpConnectionManager();
}
@Bean
public HttpClient httpClient(MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager) {
return new HttpClient(multiThreadedHttpConnectionManager);
}
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
@Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
@Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
@Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
@Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
@Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
@Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
@Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource("classpath:keystore.jks");
Map<String, String> passwords = new HashMap<>();
passwords.put(ALIAS, STORE_PASS);
return new JKSKeyManager(storeFile, STORE_PASS, passwords, ALIAS);
}
@Bean
public WebSSOProfileOptions webSSOProfileOptions() {
return new WebSSOProfileOptions();
}
@Bean
public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
return samlEntryPoint;
}
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
// #######
// ####### In theory, by setting these keys, the signature block should appear,
// ####### but, that didn't work for me
// #######
extendedMetadata.setSignMetadata(true);
extendedMetadata.setSigningKey(ALIAS);
extendedMetadata.setEncryptionKey(ALIAS);
// #######
return extendedMetadata;
}
@Bean
public ExtendedMetadataDelegate extendedMetadataDelegate(HttpClient httpClient,
ParserPool parserPool,
ExtendedMetadata extendedMetadata) throws MetadataProviderException {
Timer backgroundTaskTimer = new Timer(true);
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(backgroundTaskTimer,
httpClient,
METADATA_URL);
httpMetadataProvider.setParserPool(parserPool);
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider,
extendedMetadata);
extendedMetadataDelegate.setMetadataTrustCheck(false);
return extendedMetadataDelegate;
}
@Bean
public CachingMetadataManager metadata(ExtendedMetadataDelegate extendedMetadataDelegate) throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(extendedMetadataDelegate);
return new CachingMetadataManager(providers);
}
@Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setExtendedMetadata(extendedMetadata);
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager);
return metadataGenerator;
}
@Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
@Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
@Bean
public SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter(SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
AuthenticationManager authenticationManager,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOHoKProcessingFilter;
}
@Bean
public SAMLProcessingFilter samlProcessingFilter(AuthenticationManager authenticationManager,
SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOProcessingFilter;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter(MetadataGenerator metadataGenerator) {
return new MetadataGeneratorFilter(metadataGenerator);
}
@Bean
public SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutProcessingFilter(simpleUrlLogoutSuccessHandler, securityContextLogoutHandler);
}
@Bean
public SAMLLogoutFilter samlLogoutFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutFilter(simpleUrlLogoutSuccessHandler,
new LogoutHandler[]{securityContextLogoutHandler},
new LogoutHandler[]{securityContextLogoutHandler});
}
@Bean
public HTTPArtifactBinding artifactBinding(HTTPSOAP11Binding httpSOAP11Binding,
HttpClient httpClient,
ParserPool parserPool,
VelocityEngine velocityEngine) {
ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient);
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(httpSOAP11Binding));
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile);
}
@Bean
public HTTPSOAP11Binding httpSOAP11Binding(ParserPool parserPool) {
return new HTTPSOAP11Binding(parserPool);
}
@Bean
public HTTPPostBinding httpPostBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPPostBinding(parserPool, velocityEngine);
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) {
return new HTTPRedirectDeflateBinding(parserPool);
}
@Bean
public HTTPPAOS11Binding httpPAOS11Binding(ParserPool parserPool) {
return new HTTPPAOS11Binding(parserPool);
}
@Bean
public SAMLProcessorImpl processor(HTTPRedirectDeflateBinding httpRedirectDeflateBinding,
HTTPPostBinding httpPostBinding,
HTTPArtifactBinding httpArtifactBinding,
HTTPSOAP11Binding httpSOAP11Binding,
HTTPPAOS11Binding httpPAOS11Binding) {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding);
bindings.add(httpPostBinding);
bindings.add(httpArtifactBinding);
bindings.add(httpSOAP11Binding);
bindings.add(httpPAOS11Binding);
return new SAMLProcessorImpl(bindings);
}
@Bean
public FilterChainProxy filterChainProxy(SAMLEntryPoint samlEntryPoint,
SAMLLogoutFilter samlLogoutFilter,
MetadataDisplayFilter metadataDisplayFilter,
SAMLProcessingFilter samlProcessingFilter,
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter,
SAMLLogoutProcessingFilter samlLogoutProcessingFilter) throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(samlAuthenticationProvider);
}
}
如果有人能将我推向正确的方向,我将不胜感激。
谢谢。
更新
MetadataGenerator
生成的我的 SP 元数据如下所示:-
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="https___localhost_8443_helix_saml_metadata" entityID="https://localhost:8443/helix/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICx....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICxz....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SSO" index="0" isDefault="true"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://localhost:8443/helix/saml/SSO" index="1"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
我还想指出,我使用的是 SHA256withRSA 而不是 SHA1withRSA。因此,我将默认的 SAMLBootstrap
bean 替换为以下内容:-
public final class CustomSamlBootstrap extends SAMLBootstrap {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
super.postProcessBeanFactory(beanFactory);
BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
}
}
更新 2
我通过修改 WebSSOProfileOptions
.
让它工作
@Bean
public WebSSOProfileOptions webSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
// Added this line
webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
return webSSOProfileOptions;
}
您使用什么绑定向 IDP 发送请求?如果是 HTTP 请求,SAML 标准要求在传递消息之前删除签名。然后对序列化请求执行签名并作为 GET 参数发送。
我相信你应该设置:
@Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
// ... other configuration here
metadataGenerator.setRequestSigned ( true );
return metadataGenerator;
}
在 ExtendedMetadata 中,您只设置了要签名的元数据文件。这不会影响 AuthnRequest 的签名。
我很难让 Spring 安全 SAML 与 ADFS 2.0 一起工作。
根据我当前的配置,生成的 AuthnRequest
如下所示:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO"
Destination="https://server/adfs/ls/"
ForceAuthn="false" ID="a14edaf38ih92bi8acji5a1664a80e"
IsPassive="false" IssueInstant="2016-02-15T21:05:57.980Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<saml2p:Scoping ProxyCount="2"/>
</saml2p:AuthnRequest>
但是,它会导致 ADFS 端出错:-
Microsoft.IdentityModel.Protocols.XmlSignature.SignatureVerificationFailedException: MSIS0038: SAML Message has wrong signature. Issuer: 'https://localhost:8443/helix/saml/metadata'.
at Microsoft.IdentityServer.Protocols.Saml.Contract.SamlContractUtility.CreateSamlMessage(MSISSamlBindingMessage message)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.CreateErrorMessage(CreateErrorMessageRequest createErrorMessageRequest)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.ProcessRequest(Message requestMessage)
我的安全团队告诉我,我的 AuthnRequest
应该看起来像这样:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO" Destination="https://server/adfs/ls/" ID="_e082771303738e4e6872e8d5711446d4" IssueInstant="2016-02-15T19:51:50.627Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#_e082771303738e4e6872e8d5711446d4">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>S8r/XbIhlFGFSMfLoSt/7IlksiI=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>TT4n3==...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIC8z...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<saml2p:RequestedAuthnContext Comparison="exact">
<saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
...但是,我无法尝试让 <Signature>
块出现在我的 AuthnRequest
.
我当前的 Spring 安全 SAML 配置如下所示...我很抱歉它相当冗长,但我不确定除了整个配置之外还应该在此处粘贴什么。
@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {
private static final String METADATA_URL = "https://server/federationmetadata/2007-06/federationmetadata.xml";
private static final String ALIAS = "apollo";
private static final String STORE_PASS = "secret";
@Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
@Autowired
private SAMLAuthenticationProvider samlAuthenticationProvider;
@Bean
public static SAMLBootstrap SAMLBootstrap() {
return new CustomSamlBootstrap();
}
@Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
@Bean
public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
return new MultiThreadedHttpConnectionManager();
}
@Bean
public HttpClient httpClient(MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager) {
return new HttpClient(multiThreadedHttpConnectionManager);
}
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
@Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
@Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
@Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
@Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
@Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
@Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
@Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource("classpath:keystore.jks");
Map<String, String> passwords = new HashMap<>();
passwords.put(ALIAS, STORE_PASS);
return new JKSKeyManager(storeFile, STORE_PASS, passwords, ALIAS);
}
@Bean
public WebSSOProfileOptions webSSOProfileOptions() {
return new WebSSOProfileOptions();
}
@Bean
public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
return samlEntryPoint;
}
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
// #######
// ####### In theory, by setting these keys, the signature block should appear,
// ####### but, that didn't work for me
// #######
extendedMetadata.setSignMetadata(true);
extendedMetadata.setSigningKey(ALIAS);
extendedMetadata.setEncryptionKey(ALIAS);
// #######
return extendedMetadata;
}
@Bean
public ExtendedMetadataDelegate extendedMetadataDelegate(HttpClient httpClient,
ParserPool parserPool,
ExtendedMetadata extendedMetadata) throws MetadataProviderException {
Timer backgroundTaskTimer = new Timer(true);
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(backgroundTaskTimer,
httpClient,
METADATA_URL);
httpMetadataProvider.setParserPool(parserPool);
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider,
extendedMetadata);
extendedMetadataDelegate.setMetadataTrustCheck(false);
return extendedMetadataDelegate;
}
@Bean
public CachingMetadataManager metadata(ExtendedMetadataDelegate extendedMetadataDelegate) throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(extendedMetadataDelegate);
return new CachingMetadataManager(providers);
}
@Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setExtendedMetadata(extendedMetadata);
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager);
return metadataGenerator;
}
@Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
@Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
@Bean
public SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter(SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
AuthenticationManager authenticationManager,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOHoKProcessingFilter;
}
@Bean
public SAMLProcessingFilter samlProcessingFilter(AuthenticationManager authenticationManager,
SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOProcessingFilter;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter(MetadataGenerator metadataGenerator) {
return new MetadataGeneratorFilter(metadataGenerator);
}
@Bean
public SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutProcessingFilter(simpleUrlLogoutSuccessHandler, securityContextLogoutHandler);
}
@Bean
public SAMLLogoutFilter samlLogoutFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutFilter(simpleUrlLogoutSuccessHandler,
new LogoutHandler[]{securityContextLogoutHandler},
new LogoutHandler[]{securityContextLogoutHandler});
}
@Bean
public HTTPArtifactBinding artifactBinding(HTTPSOAP11Binding httpSOAP11Binding,
HttpClient httpClient,
ParserPool parserPool,
VelocityEngine velocityEngine) {
ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient);
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(httpSOAP11Binding));
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile);
}
@Bean
public HTTPSOAP11Binding httpSOAP11Binding(ParserPool parserPool) {
return new HTTPSOAP11Binding(parserPool);
}
@Bean
public HTTPPostBinding httpPostBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPPostBinding(parserPool, velocityEngine);
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) {
return new HTTPRedirectDeflateBinding(parserPool);
}
@Bean
public HTTPPAOS11Binding httpPAOS11Binding(ParserPool parserPool) {
return new HTTPPAOS11Binding(parserPool);
}
@Bean
public SAMLProcessorImpl processor(HTTPRedirectDeflateBinding httpRedirectDeflateBinding,
HTTPPostBinding httpPostBinding,
HTTPArtifactBinding httpArtifactBinding,
HTTPSOAP11Binding httpSOAP11Binding,
HTTPPAOS11Binding httpPAOS11Binding) {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding);
bindings.add(httpPostBinding);
bindings.add(httpArtifactBinding);
bindings.add(httpSOAP11Binding);
bindings.add(httpPAOS11Binding);
return new SAMLProcessorImpl(bindings);
}
@Bean
public FilterChainProxy filterChainProxy(SAMLEntryPoint samlEntryPoint,
SAMLLogoutFilter samlLogoutFilter,
MetadataDisplayFilter metadataDisplayFilter,
SAMLProcessingFilter samlProcessingFilter,
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter,
SAMLLogoutProcessingFilter samlLogoutProcessingFilter) throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(samlAuthenticationProvider);
}
}
如果有人能将我推向正确的方向,我将不胜感激。
谢谢。
更新
MetadataGenerator
生成的我的 SP 元数据如下所示:-
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="https___localhost_8443_helix_saml_metadata" entityID="https://localhost:8443/helix/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICx....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICxz....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SSO" index="0" isDefault="true"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://localhost:8443/helix/saml/SSO" index="1"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
我还想指出,我使用的是 SHA256withRSA 而不是 SHA1withRSA。因此,我将默认的 SAMLBootstrap
bean 替换为以下内容:-
public final class CustomSamlBootstrap extends SAMLBootstrap {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
super.postProcessBeanFactory(beanFactory);
BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
}
}
更新 2
我通过修改 WebSSOProfileOptions
.
@Bean
public WebSSOProfileOptions webSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
// Added this line
webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
return webSSOProfileOptions;
}
您使用什么绑定向 IDP 发送请求?如果是 HTTP 请求,SAML 标准要求在传递消息之前删除签名。然后对序列化请求执行签名并作为 GET 参数发送。
我相信你应该设置:
@Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
// ... other configuration here
metadataGenerator.setRequestSigned ( true );
return metadataGenerator;
}
在 ExtendedMetadata 中,您只设置了要签名的元数据文件。这不会影响 AuthnRequest 的签名。