This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.3.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:
-
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 @Bean
s:
-
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
}