This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.2.4!

Producing <saml2:AuthnRequest>s

As stated earlier, Spring Security’s SAML 2.0 support produces a <saml2:AuthnRequest> to commence authentication with the asserting party.

Spring Security achieves this in part by registering the Saml2WebSsoAuthenticationRequestFilter in the filter chain. This filter by default responds to endpoint /saml2/authenticate/{registrationId}.

For example, if you were deployed to rp.example.com and you gave your registration an ID of okta, you could navigate to:

and the result would be a redirect that included a SAMLRequest parameter containing the signed, deflated, and encoded <saml2:AuthnRequest>.

Changing How the <saml2:AuthnRequest> Gets Stored

Saml2WebSsoAuthenticationRequestFilter uses an Saml2AuthenticationRequestRepository to persist an AbstractSaml2AuthenticationRequest instance before sending the <saml2:AuthnRequest> to the asserting party.

Additionally, Saml2WebSsoAuthenticationFilter and Saml2AuthenticationTokenConverter use an Saml2AuthenticationRequestRepository to load any AbstractSaml2AuthenticationRequest as part of authenticating the <saml2:Response>.

By default, Spring Security uses an HttpSessionSaml2AuthenticationRequestRepository, which stores the AbstractSaml2AuthenticationRequest in the HttpSession.

If you have a custom implementation of Saml2AuthenticationRequestRepository, you may configure it by exposing it as a @Bean as shown in the following example:

  • Java

  • Kotlin

@Bean
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
	return new CustomSaml2AuthenticationRequestRepository();
}
@Bean
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
    return CustomSaml2AuthenticationRequestRepository()
}

Changing How the <saml2:AuthnRequest> Gets Sent

By default, Spring Security signs each <saml2:AuthnRequest> and send it as a GET to the asserting party.

Many asserting parties don’t require a signed <saml2:AuthnRequest>. This can be configured automatically via RelyingPartyRegistrations, or you can supply it manually, like so:

Not Requiring Signed AuthnRequests
  • Boot

  • Java

  • Kotlin

spring:
  security:
    saml2:
      relyingparty:
        okta:
          identityprovider:
            entity-id: ...
            singlesignon.sign-request: false
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails(party -> party
            // ...
            .wantAuthnRequestsSigned(false)
        )
        .build();
var relyingPartyRegistration: RelyingPartyRegistration =
    RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
                // ...
                .wantAuthnRequestsSigned(false)
        }
        .build();

Otherwise, you will need to specify a private key to RelyingPartyRegistration#signingX509Credentials so that Spring Security can sign the <saml2:AuthnRequest> before sending.

By default, Spring Security will sign the <saml2:AuthnRequest> using rsa-sha256, though some asserting parties will require a different algorithm, as indicated in their metadata.

You can configure the algorithm based on the asserting party’s metadata using RelyingPartyRegistrations.

Or, you can provide it manually:

  • Java

  • Kotlin

String metadataLocation = "classpath:asserting-party-metadata.xml";
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
        // ...
        .assertingPartyDetails((party) -> party
            // ...
            .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
        )
        .build();
var metadataLocation = "classpath:asserting-party-metadata.xml"
var relyingPartyRegistration: RelyingPartyRegistration =
    RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
        // ...
        .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
                // ...
                .signingAlgorithms { sign: MutableList<String?> ->
                    sign.add(
                        SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
                    )
                }
        }
        .build();
The snippet above uses the OpenSAML SignatureConstants class to supply the algorithm name. But, that’s just for convenience. Since the datatype is String, you can supply the name of the algorithm directly.

Some asserting parties require that the <saml2:AuthnRequest> be POSTed. This can be configured automatically via RelyingPartyRegistrations, or you can supply it manually, like so:

  • Java

  • Kotlin

RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails(party -> party
            // ...
            .singleSignOnServiceBinding(Saml2MessageBinding.POST)
        )
        .build();
var relyingPartyRegistration: RelyingPartyRegistration? =
    RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
            // ...
            .singleSignOnServiceBinding(Saml2MessageBinding.POST)
        }
        .build()

Customizing OpenSAML’s AuthnRequest Instance

There are a number of reasons that you may want to adjust an AuthnRequest. For example, you may want ForceAuthN to be set to true, which Spring Security sets to false by default.

If you don’t need information from the HttpServletRequest to make your decision, then the easiest way is to register a custom AuthnRequestMarshaller with OpenSAML. This will give you access to post-process the AuthnRequest instance before it’s serialized.

But, if you do need something from the request, then you can use create a custom Saml2AuthenticationRequestContext implementation and then a Converter<Saml2AuthenticationRequestContext, AuthnRequest> to build an AuthnRequest yourself, like so:

  • Java

  • Kotlin

@Component
public class AuthnRequestConverter implements
        Converter<Saml2AuthenticationRequestContext, AuthnRequest> {

    private final AuthnRequestBuilder authnRequestBuilder;
    private final IssuerBuilder issuerBuilder;

    // ... constructor

    public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
        MySaml2AuthenticationRequestContext myContext = (MySaml2AuthenticationRequestContext) context;
        Issuer issuer = issuerBuilder.buildObject();
        issuer.setValue(myContext.getIssuer());

        AuthnRequest authnRequest = authnRequestBuilder.buildObject();
        authnRequest.setIssuer(issuer);
        authnRequest.setDestination(myContext.getDestination());
        authnRequest.setAssertionConsumerServiceURL(myContext.getAssertionConsumerServiceUrl());

        // ... additional settings

        authRequest.setForceAuthn(myContext.getForceAuthn());
        return authnRequest;
    }
}
@Component
class AuthnRequestConverter : Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
    private val authnRequestBuilder: AuthnRequestBuilder? = null
    private val issuerBuilder: IssuerBuilder? = null

    // ... constructor
    override fun convert(context: Saml2AuthenticationRequestContext): AuthnRequest {
        val myContext: MySaml2AuthenticationRequestContext = context
        val issuer: Issuer = issuerBuilder.buildObject()
        issuer.value = myContext.getIssuer()
        val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
        authnRequest.issuer = issuer
        authnRequest.destination = myContext.getDestination()
        authnRequest.assertionConsumerServiceURL = myContext.getAssertionConsumerServiceUrl()

        // ... additional settings
        authRequest.setForceAuthn(myContext.getForceAuthn())
        return authnRequest
    }
}

Then, you can construct your own Saml2AuthenticationRequestContextResolver and Saml2AuthenticationRequestFactory and publish them as @Beans:

  • Java

  • Kotlin

@Bean
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
    Saml2AuthenticationRequestContextResolver resolver =
            new DefaultSaml2AuthenticationRequestContextResolver();
    return request -> {
        Saml2AuthenticationRequestContext context = resolver.resolve(request);
        return new MySaml2AuthenticationRequestContext(context, request.getParameter("force") != null);
    };
}

@Bean
Saml2AuthenticationRequestFactory authenticationRequestFactory(
        AuthnRequestConverter authnRequestConverter) {

    OpenSaml4AuthenticationRequestFactory authenticationRequestFactory =
            new OpenSaml4AuthenticationRequestFactory();
    authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
    return authenticationRequestFactory;
}
@Bean
open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
    val resolver: Saml2AuthenticationRequestContextResolver = DefaultSaml2AuthenticationRequestContextResolver()
    return Saml2AuthenticationRequestContextResolver { request: HttpServletRequest ->
        val context = resolver.resolve(request)
        MySaml2AuthenticationRequestContext(
            context,
            request.getParameter("force") != null
        )
    }
}

@Bean
open fun authenticationRequestFactory(
    authnRequestConverter: AuthnRequestConverter?
): Saml2AuthenticationRequestFactory? {
    val authenticationRequestFactory = OpenSaml4AuthenticationRequestFactory()
    authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
    return authenticationRequestFactory
}