|
@@ -29,10 +29,9 @@ import org.apache.commons.logging.LogFactory;
|
|
|
|
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.HttpHeaders;
|
|
|
|
+import org.springframework.http.MediaType;
|
|
import org.springframework.http.server.ServletServerHttpResponse;
|
|
import org.springframework.http.server.ServletServerHttpResponse;
|
|
import org.springframework.security.core.Authentication;
|
|
import org.springframework.security.core.Authentication;
|
|
-import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
|
|
|
-import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
|
|
|
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
|
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
|
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
|
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
|
@@ -40,6 +39,8 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMe
|
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
|
import org.springframework.security.web.util.UrlUtils;
|
|
import org.springframework.security.web.util.UrlUtils;
|
|
import org.springframework.util.Assert;
|
|
import org.springframework.util.Assert;
|
|
|
|
+import org.springframework.util.LinkedMultiValueMap;
|
|
|
|
+import org.springframework.util.MultiValueMap;
|
|
import org.springframework.web.client.RestClientException;
|
|
import org.springframework.web.client.RestClientException;
|
|
import org.springframework.web.client.RestOperations;
|
|
import org.springframework.web.client.RestOperations;
|
|
import org.springframework.web.client.RestTemplate;
|
|
import org.springframework.web.client.RestTemplate;
|
|
@@ -51,25 +52,29 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|
* Back-Channel Logout Token and invalidates each one.
|
|
* Back-Channel Logout Token and invalidates each one.
|
|
*
|
|
*
|
|
* @author Josh Cummings
|
|
* @author Josh Cummings
|
|
- * @since 6.2
|
|
|
|
|
|
+ * @since 6.4
|
|
* @see <a target="_blank" href=
|
|
* @see <a target="_blank" href=
|
|
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
|
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
|
* Spec</a>
|
|
* Spec</a>
|
|
*/
|
|
*/
|
|
-final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
|
|
|
|
+public final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
|
|
|
|
private final Log logger = LogFactory.getLog(getClass());
|
|
private final Log logger = LogFactory.getLog(getClass());
|
|
|
|
|
|
- private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
|
|
|
|
|
+ private final OidcSessionRegistry sessionRegistry;
|
|
|
|
|
|
private RestOperations restOperations = new RestTemplate();
|
|
private RestOperations restOperations = new RestTemplate();
|
|
|
|
|
|
- private String logoutUri = "{baseScheme}://localhost{basePort}/logout";
|
|
|
|
|
|
+ private String logoutUri = "{baseUrl}/logout/connect/back-channel/{registrationId}";
|
|
|
|
|
|
private String sessionCookieName = "JSESSIONID";
|
|
private String sessionCookieName = "JSESSIONID";
|
|
|
|
|
|
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
|
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
|
|
|
|
|
|
|
+ public OidcBackChannelLogoutHandler(OidcSessionRegistry sessionRegistry) {
|
|
|
|
+ this.sessionRegistry = sessionRegistry;
|
|
|
|
+ }
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
|
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
|
if (!(authentication instanceof OidcBackChannelLogoutAuthentication token)) {
|
|
if (!(authentication instanceof OidcBackChannelLogoutAuthentication token)) {
|
|
@@ -86,7 +91,7 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
for (OidcSessionInformation session : sessions) {
|
|
for (OidcSessionInformation session : sessions) {
|
|
totalCount++;
|
|
totalCount++;
|
|
try {
|
|
try {
|
|
- eachLogout(request, session);
|
|
|
|
|
|
+ eachLogout(request, token, session);
|
|
invalidatedCount++;
|
|
invalidatedCount++;
|
|
}
|
|
}
|
|
catch (RestClientException ex) {
|
|
catch (RestClientException ex) {
|
|
@@ -103,18 +108,23 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private void eachLogout(HttpServletRequest request, OidcSessionInformation session) {
|
|
|
|
|
|
+ private void eachLogout(HttpServletRequest request, OidcBackChannelLogoutAuthentication token,
|
|
|
|
+ OidcSessionInformation session) {
|
|
HttpHeaders headers = new HttpHeaders();
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.add(HttpHeaders.COOKIE, this.sessionCookieName + "=" + session.getSessionId());
|
|
headers.add(HttpHeaders.COOKIE, this.sessionCookieName + "=" + session.getSessionId());
|
|
for (Map.Entry<String, String> credential : session.getAuthorities().entrySet()) {
|
|
for (Map.Entry<String, String> credential : session.getAuthorities().entrySet()) {
|
|
headers.add(credential.getKey(), credential.getValue());
|
|
headers.add(credential.getKey(), credential.getValue());
|
|
}
|
|
}
|
|
- String logout = computeLogoutEndpoint(request);
|
|
|
|
- HttpEntity<?> entity = new HttpEntity<>(null, headers);
|
|
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
|
|
+ String logout = computeLogoutEndpoint(request, token);
|
|
|
|
+ MultiValueMap<String, String> body = new LinkedMultiValueMap();
|
|
|
|
+ body.add("logout_token", token.getPrincipal().getTokenValue());
|
|
|
|
+ body.add("_spring_security_internal_logout", "true");
|
|
|
|
+ HttpEntity<?> entity = new HttpEntity<>(body, headers);
|
|
this.restOperations.postForEntity(logout, entity, Object.class);
|
|
this.restOperations.postForEntity(logout, entity, Object.class);
|
|
}
|
|
}
|
|
|
|
|
|
- String computeLogoutEndpoint(HttpServletRequest request) {
|
|
|
|
|
|
+ String computeLogoutEndpoint(HttpServletRequest request, OidcBackChannelLogoutAuthentication token) {
|
|
// @formatter:off
|
|
// @formatter:off
|
|
UriComponents uriComponents = UriComponentsBuilder
|
|
UriComponents uriComponents = UriComponentsBuilder
|
|
.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
|
.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
|
@@ -137,6 +147,9 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
int port = uriComponents.getPort();
|
|
int port = uriComponents.getPort();
|
|
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
|
|
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
|
|
|
|
|
|
|
|
+ String registrationId = token.getClientRegistration().getRegistrationId();
|
|
|
|
+ uriVariables.put("registrationId", registrationId);
|
|
|
|
+
|
|
return UriComponentsBuilder.fromUriString(this.logoutUri)
|
|
return UriComponentsBuilder.fromUriString(this.logoutUri)
|
|
.buildAndExpand(uriVariables)
|
|
.buildAndExpand(uriVariables)
|
|
.toUriString();
|
|
.toUriString();
|
|
@@ -158,34 +171,13 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Use this {@link OidcSessionRegistry} to identify sessions to invalidate. Note that
|
|
|
|
- * this class uses
|
|
|
|
- * {@link OidcSessionRegistry#removeSessionInformation(OidcLogoutToken)} to identify
|
|
|
|
- * sessions.
|
|
|
|
- * @param sessionRegistry the {@link OidcSessionRegistry} to use
|
|
|
|
- */
|
|
|
|
- void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
|
|
|
- Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
|
|
|
- this.sessionRegistry = sessionRegistry;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Use this {@link RestOperations} to perform the per-session back-channel logout
|
|
|
|
- * @param restOperations the {@link RestOperations} to use
|
|
|
|
- */
|
|
|
|
- void setRestOperations(RestOperations restOperations) {
|
|
|
|
- Assert.notNull(restOperations, "restOperations cannot be null");
|
|
|
|
- this.restOperations = restOperations;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* Use this logout URI for performing per-session logout. Defaults to {@code /logout}
|
|
* Use this logout URI for performing per-session logout. Defaults to {@code /logout}
|
|
* since that is the default URI for
|
|
* since that is the default URI for
|
|
* {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
|
|
* {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
|
|
* @param logoutUri the URI to use
|
|
* @param logoutUri the URI to use
|
|
*/
|
|
*/
|
|
- void setLogoutUri(String logoutUri) {
|
|
|
|
|
|
+ public void setLogoutUri(String logoutUri) {
|
|
Assert.hasText(logoutUri, "logoutUri cannot be empty");
|
|
Assert.hasText(logoutUri, "logoutUri cannot be empty");
|
|
this.logoutUri = logoutUri;
|
|
this.logoutUri = logoutUri;
|
|
}
|
|
}
|
|
@@ -197,7 +189,7 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
|
* Note that if you are using Spring Session, this likely needs to change to SESSION.
|
|
* Note that if you are using Spring Session, this likely needs to change to SESSION.
|
|
* @param sessionCookieName the cookie name to use
|
|
* @param sessionCookieName the cookie name to use
|
|
*/
|
|
*/
|
|
- void setSessionCookieName(String sessionCookieName) {
|
|
|
|
|
|
+ public void setSessionCookieName(String sessionCookieName) {
|
|
Assert.hasText(sessionCookieName, "clientSessionCookieName cannot be empty");
|
|
Assert.hasText(sessionCookieName, "clientSessionCookieName cannot be empty");
|
|
this.sessionCookieName = sessionCookieName;
|
|
this.sessionCookieName = sessionCookieName;
|
|
}
|
|
}
|