mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
294 lines
11 KiB
Plaintext
294 lines
11 KiB
Plaintext
[[servlet-saml2login-sp-initiated-factory]]
|
|
= 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 `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
|
|
|
|
`https://rp.example.org/saml2/authenticate/ping`
|
|
|
|
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
|
|
|
|
[[servlet-saml2login-store-authn-request]]
|
|
== Changing How the `<saml2:AuthnRequest>` Gets Stored
|
|
|
|
`Saml2WebSsoAuthenticationRequestFilter` uses an `Saml2AuthenticationRequestRepository` to persist an `AbstractSaml2AuthenticationRequest` instance before xref:servlet/saml2/login/authentication-requests.adoc#servlet-saml2login-sp-initiated-factory[sending the `<saml2:AuthnRequest>`] to the asserting party.
|
|
|
|
Additionally, `Saml2WebSsoAuthenticationFilter` and `Saml2AuthenticationTokenConverter` use an `Saml2AuthenticationRequestRepository` to load any `AbstractSaml2AuthenticationRequest` as part of xref:servlet/saml2/login/authentication.adoc#servlet-saml2login-authenticate-responses[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
|
|
[source,java,role="primary"]
|
|
----
|
|
@Bean
|
|
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
|
|
return new CustomSaml2AuthenticationRequestRepository();
|
|
}
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@Bean
|
|
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
|
|
return CustomSaml2AuthenticationRequestRepository()
|
|
}
|
|
----
|
|
====
|
|
|
|
[[servlet-saml2login-sp-initiated-factory-signing]]
|
|
== 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
|
|
[source,yaml,role="primary"]
|
|
----
|
|
spring:
|
|
security:
|
|
saml2:
|
|
relyingparty:
|
|
okta:
|
|
identityprovider:
|
|
entity-id: ...
|
|
singlesignon.sign-request: false
|
|
----
|
|
|
|
.Java
|
|
[source,java,role="secondary"]
|
|
----
|
|
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
|
|
// ...
|
|
.assertingPartyDetails(party -> party
|
|
// ...
|
|
.wantAuthnRequestsSigned(false)
|
|
)
|
|
.build();
|
|
----
|
|
|
|
.Kotlin
|
|
[source,java,role="secondary"]
|
|
----
|
|
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.
|
|
|
|
[[servlet-saml2login-sp-initiated-factory-algorithm]]
|
|
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 xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[metadata using `RelyingPartyRegistrations`].
|
|
|
|
Or, you can provide it manually:
|
|
|
|
====
|
|
.Java
|
|
[source,java,role="primary"]
|
|
----
|
|
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();
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
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();
|
|
----
|
|
====
|
|
|
|
NOTE: 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.
|
|
|
|
[[servlet-saml2login-sp-initiated-factory-binding]]
|
|
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
|
|
[source,java,role="primary"]
|
|
----
|
|
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
|
|
// ...
|
|
.assertingPartyDetails(party -> party
|
|
// ...
|
|
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
|
|
)
|
|
.build();
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
var relyingPartyRegistration: RelyingPartyRegistration? =
|
|
RelyingPartyRegistration.withRegistrationId("okta")
|
|
// ...
|
|
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
|
|
// ...
|
|
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
|
|
}
|
|
.build()
|
|
----
|
|
====
|
|
|
|
[[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
|
|
== 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 xref:servlet/saml2/login/overview.adoc#servlet-saml2login-opensaml-customization[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
|
|
[source,java,role="primary"]
|
|
----
|
|
@Component
|
|
public class AuthnRequestConverter implements
|
|
Converter<MySaml2AuthenticationRequestContext, 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;
|
|
}
|
|
}
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@Component
|
|
class AuthnRequestConverter : Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
|
|
private val authnRequestBuilder: AuthnRequestBuilder? = null
|
|
private val issuerBuilder: IssuerBuilder? = null
|
|
|
|
// ... constructor
|
|
override fun convert(context: MySaml2AuthenticationRequestContext): 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
|
|
[source,java,role="primary"]
|
|
----
|
|
@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;
|
|
}
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@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
|
|
}
|
|
----
|
|
====
|
|
|