Skip to content
Ashish.
All posts
Diagram illustrating the XML assertion flow between an Identity Provider and a Spring Boot Service Provider.

Spring Security SAML Extension: Enterprise SSO Integration

This guide covers enterprise SSO integration using Spring Security SAML, SAML service provider setup, and Spring SAML migration strategies.

By Ashish SrivastavaPart 6 of Spring Security Deep Dive Series

Spring Security SAML Extension: Enterprise SSO Integration

Part 6 of the Spring Security Deep Dive Series

The core mechanism of Single Sign-On (SSO) in enterprise environments is not merely "logging in once," but the cryptographic validation of a signed XML assertion that travels from an Identity Provider (IdP) to a Service Provider (SP). In the Spring ecosystem, this involves a strict sequence where the application acts as the SP, generating metadata that tells the IdP where to send authentication requests, and then parsing the incoming SAML Response to construct a local Authentication object. Unlike OAuth2, which relies on JSON tokens and opaque access codes, SAML requires the application to understand the XML structure, the X.509 certificate chain used to sign the assertion, and the specific HTTP bindings (POST or Redirect) used to transport the data.

The SAML Handshake Mechanism

When a user attempts to access a protected resource, the Spring Security filter chain intercepts the request and initiates the SAML flow. The mechanism begins with the Service Provider (SP) generating a SAMLRequest. This is a Base64-encoded, zlib-compressed XML document containing a AuthnRequest element. Crucially, this request must be signed by the SP's private key to prove identity to the IdP. The IdP receives this request, authenticates the user (potentially via a browser popup if the user is already logged in elsewhere), and generates a SAMLResponse.

This response contains the user's identity attributes (like email, groups, or roles) wrapped in a <saml:Assertion>. This assertion is signed by the IdP's private key. The Spring Security SAML extension must verify this signature using the IdP's public certificate before trusting the contained attributes. If the signature verification fails, the request is rejected immediately at the cryptographic layer, preventing token injection attacks. The flow concludes when Spring Security converts the validated SAMLResponse into a Saml2AuthenticationToken, which is then placed in the SecurityContext.

This mechanism differs significantly from the legacy Spring SAML implementation. In Spring SAML 1.x, the framework handled much of the XML parsing internally via a complex filter chain that assumed a monolithic application context. In Spring Security 5.7+ (and Spring Boot 3), the focus shifts to a more modular Saml2 stack where the developer explicitly configures the metadata resolver and the authentication provider.

Spring Boot 3 SP Configuration

To implement this in a modern Spring Boot application, you must configure the Saml2Registration bean. This bean defines the SP's identity, including the entity ID, the single sign-on service URL, and the single logout service URL. The critical component here is the metadata attribute, which points to a Saml2MetadataResolver. This resolver fetches the metadata XML from your IdP (e.g., Okta, Azure AD, PingIdentity) and extracts the necessary public keys and endpoints.

Consider a scenario where "Acme Corp" integrates with "Global IdP". The application needs to know exactly where to send the login request.

@Bean
public Saml2Registration acmeSamlRegistration() {
    return Saml2Registration.withId("acme-saml")
        .entityId("https://acme.example.com/saml/metadata")
        .singleSignOnServiceUrl("https://acme.example.com/saml/sso")
        .singleLogoutServiceUrl("https://acme.example.com/saml/slo")
        .resolver(new StaticMetadataResolver(
            new ClassPathResource("global-idp-metadata.xml")))
        .build();
}

Once the registration is defined, the Saml2WebSecurityConfiguration is used to wire up the authentication provider. The Saml2AuthenticationProvider validates the incoming response using the registration configuration. It then invokes the Saml2AuthenticationConverter to transform the validated response into a Saml2AuthenticationToken. They are distinct components; the provider does not 'take' the converter to perform the transformation.

However, the real power lies in mapping the IdP's attributes to Spring authorities. The IdP might send a group attribute named urn:oid:2.5.4.15 containing "Admins". You must define a GrantedAuthorityMapper to translate this string into a ROLE_ADMIN. Without this explicit mapping, the application receives a generic Saml2AuthenticationToken with no permissions, even if the user is successfully authenticated.

@Bean
public Saml2AuthenticationConverter samlAuthenticationConverter() {
    return new DefaultSaml2AuthenticationConverter();
}
 
@Bean
public GrantedAuthorityMapper samlAuthorityMapper() {
    return (authentication) -> {
        var groups = authentication.getPrincipal().getAttributes().get("groups");
        if (groups != null) {
            return ((List<String>) groups).stream()
                .map(g -> new SimpleGrantedAuthority("ROLE_" + g.toUpperCase()))
                .collect(Collectors.toList());
        }
        return Collections.emptyList();
    };
}

This configuration ensures that the trust boundary is clearly defined: the IdP provides the identity, and the SP provides the authorization logic.

Migration from Legacy Spring SAML

Many enterprises are currently running Spring SAML 1.x, which is based on the older Spring Security 4/5 architecture. Migrating to Spring Security 5.7+ (Spring Boot 3) is not a drop-in replacement; it requires a fundamental shift in how the security chain is constructed. The legacy SamlWebSecurityAdapter and SamlFilter were tightly coupled to the servlet API and assumed a specific XML processing pipeline. The new Saml2 stack is designed to be stateless and compatible with reactive programming models, though it is most commonly used in MVC applications.

The primary friction point in migration is the removal of the automatic metadata generation. In Spring SAML 1.x, the SamlWebSecurityAdapter could often infer metadata generation from the spring-security-config dependency. In the new stack, you must explicitly provide a Saml2MetadataResolver and ensure the Saml2Registration is wired into the SecurityFilterChain.

Furthermore, the legacy stack relied heavily on the SamlMessageChannel to handle the message flow. The new stack uses Saml2AuthenticationConverter and Saml2AuthenticationProvider directly. If you are migrating, you must manually map the old UserDetailsService logic to the new Saml2AuthenticationConverter's convert method. Both legacy and new stacks support dynamic user creation; the difference lies in the configuration model (annotation vs XML) and the lack of SamlWebSecurityAdapter in the new stack, rather than an assumption of a pre-existing database.

A common failure mode during migration is the misconfiguration of the AssertionConsumerService URL. In the legacy system, this was often hardcoded in the web.xml or inferred. In the new system, the URL must match exactly what is registered in the IdP's metadata. A mismatch results in the IdP sending the response to a non-existent endpoint, causing the SAML flow to abort before the assertion is ever processed.

Conclusion

Implementing Enterprise SSO with Spring Security SAML requires a thorough understanding of the XML assertion lifecycle, from the initial AuthnRequest to the final authority mapping. The mechanism is rigid: the SP must trust the IdP's signature, and the application must correctly parse the attributes to enforce access control. While the migration from legacy Spring SAML introduces complexity due to the architectural shift from XML-heavy filters to the modular Saml2 stack, the resulting system is more robust, testable, and aligned with modern Spring Boot principles. The tradeoff is the upfront effort required to configure the metadata resolvers and authority mappers, but the payoff is a secure, standards-compliant SSO integration that scales with enterprise identity requirements.

FAQ

Q: How do I handle dynamic user creation in Spring Security SAML? A: Both the legacy and new stacks support dynamic user creation. In the new Saml2 stack, you typically implement this logic within your Saml2AuthenticationConverter or by injecting a custom UserDetailsService that creates a user record if one does not exist upon successful authentication.

Q: What is the difference between SAML 1.x and 2.x in Spring? A: SAML 1.x in Spring relied on the SamlWebSecurityAdapter and SamlFilter, which were tightly coupled to the legacy Spring Security architecture. SAML 2.x (Spring Security 5.7+) uses the Saml2Registration, Saml2AuthenticationProvider, and Saml2AuthenticationConverter, offering a more modular, stateless, and reactive-friendly approach.

Q: Can I use Spring Security SAML with a reactive web application? A: Yes, the Saml2 stack is designed to be stateless and compatible with reactive programming models. However, the most common and straightforward usage pattern is within standard MVC applications, as the SAML protocol itself relies heavily on HTTP redirects and session state which can be complex in purely reactive flows.

Common Pitfalls

  1. Metadata URL Mismatches: The most frequent error is registering an Assertion Consumer Service (ACS) URL in the IdP that does not match the URL configured in your Spring Saml2Registration. This causes the IdP to redirect the SAML response to a non-existent endpoint.
  2. Clock Skew Issues: SAML assertions have strict validity windows (NotBefore and NotOnOrAfter). If the clock on your Spring Boot server is not synchronized with the IdP via NTP, valid assertions may be rejected as expired or not yet valid.
  3. Incorrect Signature Verification: Failing to import the correct IdP public key into your metadata resolver or using the wrong key version (e.g., signing key vs. encryption key) will cause immediate signature verification failures, blocking all authentication attempts.

Practical Takeaways

  • Trust Boundary: Remember that the IdP is the source of truth for identity, while your Spring application is solely responsible for authorization logic based on the received attributes.
  • Explicit Configuration: Unlike legacy frameworks, the Saml2 stack requires you to explicitly define every component, from the metadata resolver to the authority mapper; implicit defaults are removed.
  • Validation First: Always prioritize signature verification and clock skew checks in your configuration. A valid assertion that fails these checks is a security risk, not a valid login.

Related posts