Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>
Posted
技术标签:
【中文标题】Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>【英文标题】:Spring Security SAML: Getting <Signature> block to appear in <AuthnRequest> 【发布时间】:2016-05-26 22:54:36 【问题描述】:我很难让 Spring Security 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 Security 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;
【问题讨论】:
【参考方案1】:您使用什么绑定来向 IDP 发送请求?如果是 HTTP 请求,SAML 标准要求在传递消息之前删除他的签名。然后对序列化的请求执行签名并作为 GET 参数发送。
【讨论】:
感谢您的回复。如何验证/检查我正在使用的绑定?我以为我一直在通过 HTTP-Post 发送,但我认为我的绑定是错误的,因为我的安全团队告诉我 ADFS 正在以查询参数字符串的形式接收我的响应,这意味着我使用的是 GET 而不是 POST。 对于我的WebSSOProfileOptions
,绑定设置为urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
。当设置为较早或urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
时,AuthnRequest
中的ProtocolBinding
始终显示为urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
。
我通过添加生成的 SP 元数据进行了更新,并且我使用了 SHA256withRSA 而不是 SHA1withRSA。谢谢。【参考方案2】:
我相信你应该设置:
@Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager)
MetadataGenerator metadataGenerator = new MetadataGenerator();
// ... other configuration here
metadataGenerator.setRequestSigned ( true );
return metadataGenerator;
在 ExtendedMetadata 中,您只是设置要签名的元数据文件。这不会影响 AuthnRequest 的签名。
【讨论】:
以上是关于Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security SAML:让 <Signature> 块出现在 <AuthnRequest>
生成 SP 元数据时出现意外的堆栈跟踪表单 Spring-Security-SAML?
Spring Security SAML2 未找到 entityID
未抛出 Spring Security SAML DisabledException