1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Revert "Add Saml2LogoutConfigurer"

This reverts commit 6f52baba29.
This commit is contained in:
Josh Cummings
2021-04-12 14:43:19 -06:00
parent 44763345d3
commit 4e81bbe386
5 changed files with 119 additions and 1314 deletions
@@ -1074,7 +1074,9 @@ To use Spring Security's SAML 2.0 Single Logout feature, you will need the follo
* Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
* Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
You can begin from the initial minimal example and add the following configuration:
==== RP-Initiated Single Logout
Given those, then for RP-initiated Single Logout, you can begin from the initial minimal example and add the following configuration:
[source,java]
----
@@ -1103,15 +1105,28 @@ SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository re
.anyRequest().authenticated()
)
.saml2Login(withDefaults())
.saml2Logout(withDefaults()); <2>
.logout((logout) -> logout
.logoutUrl("/saml2/logout")
.logoutSuccessHandler(successHandler))
.addFilterBefore(new Saml2LogoutResponseFilter(logoutHandler), CsrfFilter.class);
return http.build();
}
private LogoutSuccessHandler logoutRequestSuccessHandler(RelyingPartyRegistrationResolver registrationResolver) { <2>
OpenSaml4LogoutRequestResolver logoutRequestResolver = new OpenSaml4LogoutRequestResolver(registrationResolver);
return new Saml2LogoutRequestSuccessHandler(logoutRequestResolver);
}
private LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { <3>
return new OpenSamlLogoutResponseHandler(relyingPartyRegistrationResolver);
}
----
<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <<servlet-saml2login-rpr-duplicated,multiple instances>>
<2> - Second, indicate that your application wants to use SAML SLO to logout the end user
<2> - Second, supply a `LogoutSuccessHandler` for initiating Single Logout, sending a `saml2:LogoutRequest` to the asserting party
<3> - Third, supply the `LogoutHandler` s needed to handle the `saml2:LogoutResponse` s sent from the asserting party.
==== Runtime Expectations
==== Runtime Expectations for RP-Initiated
Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
Your application will then do the following:
@@ -1122,6 +1137,63 @@ Your application will then do the following:
4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
5. Redirect to any configured successful logout endpoint
[TIP]
If your asserting party does not send `<saml2:LogoutResponse>` s when logout is complete, the asserting party can still send a `POST /saml2/logout` and then there is no need to configure the `Saml2LogoutResponseHandler`.
==== AP-Initiated Single Logout
Instead of RP-initiated Single Logout, you can again begin from the initial minimal example and add the following configuration to achieve AP-initiated Single Logout:
[source,java]
----
@Value("${private.key}") RSAPrivateKey key;
@Value("${public.certificate}") X509Certificate certificate;
@Bean
RelyingPartyRegistrationRepository registrations() {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("id")
.signingX509Credentials((signing) -> signing.add(Saml2X509Credential.signing(key, certificate))) <1>
.build();
return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration);
}
@Bean
SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations);
LogoutHandler logoutRequestHandler = logoutRequestHandler(registrationResolver);
LogoutSuccessHandler logoutResponseSuccessHandler = logoutResponseSuccessHandler(registrationResolver);
http
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.saml2Login(withDefaults())
.addFilterBefore(new Saml2LogoutRequestFilter(logoutResponseSuccessHandler, logoutRequestHandler), CsrfFilter.class);
return http.build();
}
private LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { <2>
return new CompositeLogoutHandler(
new OpenSamlLogoutRequestHandler(relyingPartyRegistrationResolver),
new SecurityContextLogoutHandler(),
new LogoutSuccessEventPublishingLogoutHandler());
}
private LogoutSuccessHandler logoutSuccessHandler(RelyingPartyRegistrationResolver registrationResolver) { <3>
OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver(registrationResolver);
return new Saml2LogoutResponseSuccessHandler(logoutResponseResolver);
}
----
<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <<servlet-saml2login-rpr-duplicated,multiple instances>>
<2> - Second, supply the `LogoutHandler` needed to handle the `saml2:LogoutRequest` s sent from the asserting party.
<3> - Third, supply a `LogoutSuccessHandler` for completing Single Logout, sending a `saml2:LogoutResponse` to the asserting party
==== Runtime Expectations for AP-Initiated
Given the above configuration, an asserting party can send a `POST /logout/saml2` to your application that includes a `<saml2:LogoutRequest>`
Also, your application can participate in an AP-initated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
@@ -1129,6 +1201,12 @@ Also, your application can participate in an AP-initated logout when the asserti
3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>> associated with the just logged-out user
4. Send a redirect or post to the asserting party based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>>
[TIP]
If your asserting party does not expect you do send a `<saml2:LogoutResponse>` s when logout is complete, you may not need to configure a `LogoutSuccessHandler`
[NOTE]
In the event that you need to support both logout flows, you can combine the above to configurations.
=== Configuring Logout Endpoints
There are three default endpoints that Spring Security's SAML 2.0 Single Logout support exposes:
@@ -1145,12 +1223,11 @@ To reduce changes in configuration for the asserting party, you can configure th
[source,java]
----
RequestMatcher slo = new AntPathRequestMatcher("/SLOService.saml2", "GET");
Saml2LogoutResponseFilter filter = new Saml2LogoutResponseFilter(logoutHandler);
filter.setLogoutRequestMatcher(new AntPathRequestMatcher("/SLOService.saml2", "GET"));
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request.logoutRequestMatcher(slo))
.logoutResponse((response) -> response.logoutRequestMatcher(slo))
);
// ...
.addFilterBefore(filter, CsrfFilter.class);
----
=== Customizing `<saml2:LogoutRequest>` Resolution
@@ -1168,40 +1245,22 @@ To add other values, you can use delegation, like so:
[source,java]
----
@Component
public class MyOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResolver {
private final OpenSaml3LogoutRequestResolver logoutRequestResolver;
public MyOpenSamlLogoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(registrations);
this.logoutRequestResolver = new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver);
}
@Override
public OpenSamlLogoutRequestBuilder resolveLogoutRequest(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String name = ((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute");
String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
return logoutRequestResolver.resolveLogoutRequest(request, authentication) <1>
.name(name) <2>
.logoutRequest((logoutRequest) -> logoutRequest.getNameID().setFormat(format));
}
}
OpenSamlLogoutRequestResolver delegate = new OpenSamlLogoutRequestResolver(registrationResolver);
return (request, response, authentication) -> {
OpenSamlLogoutRequestBuilder builder = delegate.resolveLogoutRequest(request, response, authentication); <1>
builder.name(((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute")); <2>
builder.logoutRequest((logoutRequest) -> logoutRequest.setIssueInstant(DateTime.now()));
return builder.logoutRequest(); <3>
};
----
<1> - Spring Security applies default values to a `<saml2:LogoutRequest>`
<2> - Your application specifies customizations
<3> - You complete the invocation by calling `request()`
Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
[source,java]
----
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestResolver(myOpenSamlLogoutRequestResolver)
)
);
----
[NOTE]
Support for OpenSAML 4 is coming.
In anticipation of that, `OpenSamlLogoutRequestResolver` does not add an `IssueInstant`.
Once OpenSAML 4 support is added, the default will be able to appropriate negotiate that datatype change, meaning you will no longer have to set it.
=== Customizing `<saml2:LogoutResponse>` Resolution
@@ -1218,42 +1277,24 @@ To add other values, you can use delegation, like so:
[source,java]
----
@Component
public class MyOpenSamlLogoutResponseResolver implements Saml2LogoutRequestResolver {
private final OpenSaml3LogoutResponseResolver logoutRequestResolver;
public MyOpenSamlLogoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(registrations);
this.logoutResponseResolver = new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
}
@Override
public OpenSamlLogoutResponseBuilder resolveLogoutResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String name = ((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute");
String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
OpenSamlLogoutResponseBuilder builder = logoutResponseResolver.resolveLogoutRequest(request, authentication); <1>
if (checkOtherPrevailingConditions()) {
builder.status(StatusCode.PARTIAL_LOGOUT); <2>
}
return builder;
OpenSamlLogoutResponseResolver delegate = new OpenSamlLogoutResponseResolver(registrationResolver);
return (request, response, authentication) -> {
OpenSamlLogoutResponseBuilder builder = delegate.resolveLogoutResponse(request, response, authentication); <1>
if (checkOtherPrevailingConditions()) {
builder.status(StatusCode.PARTIAL_LOGOUT); <2>
}
}
builder.logoutResponse((logoutResponse) -> logoutResponse.setIssueInstant(DateTime.now()));
return builder.logoutResponse(); <3>
};
----
<1> - Spring Security applies default values to a `<saml2:LogoutResponse>`
<2> - Your application specifies customizations
<3> - You complete the invocation by calling `response()`
Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
[source,java]
----
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestResolver(myOpenSamlLogoutRequestResolver)
)
);
----
[NOTE]
Support for OpenSAML 4 is coming.
In anticipation of that, `OpenSamlLogoutResponseResolver` does not add an `IssueInstant`.
Once OpenSAML 4 support is added, the default will be able to appropriate negotiate that datatype change, meaning you will no longer have to set it.
=== Customizing `<saml2:LogoutRequest>` Validation
@@ -1262,37 +1303,16 @@ At this point, the validation is minimal, so you may be able to first delegate t
[source,java]
----
@Component
public class MyOpenSamlLogoutRequestHandler implements LogoutHandler {
private final Saml2LogoutRequestHandler delegate;
public MyOpenSamlLogoutRequestHandler(RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(registrations);
this.delegate = new OpenSamlLogoutRequestHandler(relyingPartyRegistrationResolver);
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) {
OpenSamlLogoutRequestHandler delegate = new OpenSamlLogoutRequestHandler(registrationResolver);
return (request, response, authentication) -> {
delegate.logout(request, response, authentication); // verify signature, issuer, destination, and principal name
LogoutRequest logoutRequest = // ... parse using OpenSAML
// perform custom validation
}
}
}
----
Then, you can supply your custom `LogoutHandler` in the DSL as follows:
[source,java]
----
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestHandler(myOpenSamlLogoutRequestHandler)
)
);
----
=== Customizing `<saml2:LogoutResponse>` Validation
To customize validation, you can implement your own `LogoutHandler`.
@@ -1300,49 +1320,12 @@ At this point, the validation is minimal, so you may be able to first delegate t
[source,java]
----
@Component
public class MyOpenSamlLogoutResponseHandler implements LogoutHandler {
private final Saml2LogoutResponseHandler delegate;
public MyOpenSamlLogoutResponseHandler(RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(registrations);
this.delegate = new OpenSamlLogoutResponseHandler(relyingPartyRegistrationResolver);
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) {
OpenSamlLogoutResponseHandler delegate = new OpenSamlLogoutResponseHandler(registrationResolver);
return (request, response, authentication) -> {
delegate.logout(request, response, authentication); // verify signature, issuer, destination, and status
LogoutResponse logoutResponse = // ... parse using OpenSAML
// perform custom validation
}
}
}
----
Then, you can supply your custom `LogoutHandler` in the DSL as follows:
[source,java]
----
http
.saml2Logout((saml2) -> saml2
.logoutResponse((response) -> response
.logoutResponseHandler(myOpenSamlLogoutResponseHandler)
)
);
----
=== Customizing `<saml2:LogoutRequest>` storage
When your application sends a `<saml2:LogoutRequest>`, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `<saml2:LogoutResponse>` can be verified.
If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:
[source,java]
----
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestRepository(myCustomLogoutRequestRepository)
)
);
----