2929
3030import org .springframework .http .HttpEntity ;
3131import org .springframework .http .HttpHeaders ;
32+ import org .springframework .http .MediaType ;
3233import org .springframework .http .server .ServletServerHttpResponse ;
3334import org .springframework .security .core .Authentication ;
34- import org .springframework .security .oauth2 .client .oidc .authentication .logout .OidcLogoutToken ;
35- import org .springframework .security .oauth2 .client .oidc .session .InMemoryOidcSessionRegistry ;
3635import org .springframework .security .oauth2 .client .oidc .session .OidcSessionInformation ;
3736import org .springframework .security .oauth2 .client .oidc .session .OidcSessionRegistry ;
3837import org .springframework .security .oauth2 .core .OAuth2Error ;
3938import org .springframework .security .oauth2 .core .http .converter .OAuth2ErrorHttpMessageConverter ;
4039import org .springframework .security .web .authentication .logout .LogoutHandler ;
4140import org .springframework .security .web .util .UrlUtils ;
4241import org .springframework .util .Assert ;
42+ import org .springframework .util .LinkedMultiValueMap ;
43+ import org .springframework .util .MultiValueMap ;
4344import org .springframework .web .client .RestClientException ;
4445import org .springframework .web .client .RestOperations ;
4546import org .springframework .web .client .RestTemplate ;
5152 * Back-Channel Logout Token and invalidates each one.
5253 *
5354 * @author Josh Cummings
54- * @since 6.2
55+ * @since 6.4
5556 * @see <a target="_blank" href=
5657 * "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
5758 * Spec</a>
5859 */
59- final class OidcBackChannelLogoutHandler implements LogoutHandler {
60+ public final class OidcBackChannelLogoutHandler implements LogoutHandler {
6061
6162 private final Log logger = LogFactory .getLog (getClass ());
6263
63- private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry () ;
64+ private final OidcSessionRegistry sessionRegistry ;
6465
6566 private RestOperations restOperations = new RestTemplate ();
6667
67- private String logoutUri = "{baseScheme}://localhost{basePort}/logout " ;
68+ private String logoutUri = "{baseUrl}/logout/connect/back-channel/{registrationId} " ;
6869
6970 private String sessionCookieName = "JSESSIONID" ;
7071
7172 private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter ();
7273
74+ public OidcBackChannelLogoutHandler (OidcSessionRegistry sessionRegistry ) {
75+ this .sessionRegistry = sessionRegistry ;
76+ }
77+
7378 @ Override
7479 public void logout (HttpServletRequest request , HttpServletResponse response , Authentication authentication ) {
7580 if (!(authentication instanceof OidcBackChannelLogoutAuthentication token )) {
@@ -86,7 +91,7 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut
8691 for (OidcSessionInformation session : sessions ) {
8792 totalCount ++;
8893 try {
89- eachLogout (request , session );
94+ eachLogout (request , token , session );
9095 invalidatedCount ++;
9196 }
9297 catch (RestClientException ex ) {
@@ -103,18 +108,23 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut
103108 }
104109 }
105110
106- private void eachLogout (HttpServletRequest request , OidcSessionInformation session ) {
111+ private void eachLogout (HttpServletRequest request , OidcBackChannelLogoutAuthentication token ,
112+ OidcSessionInformation session ) {
107113 HttpHeaders headers = new HttpHeaders ();
108114 headers .add (HttpHeaders .COOKIE , this .sessionCookieName + "=" + session .getSessionId ());
109115 for (Map .Entry <String , String > credential : session .getAuthorities ().entrySet ()) {
110116 headers .add (credential .getKey (), credential .getValue ());
111117 }
112- String logout = computeLogoutEndpoint (request );
113- HttpEntity <?> entity = new HttpEntity <>(null , headers );
118+ headers .setContentType (MediaType .APPLICATION_FORM_URLENCODED );
119+ String logout = computeLogoutEndpoint (request , token );
120+ MultiValueMap <String , String > body = new LinkedMultiValueMap ();
121+ body .add ("logout_token" , token .getPrincipal ().getTokenValue ());
122+ body .add ("_spring_security_internal_logout" , "true" );
123+ HttpEntity <?> entity = new HttpEntity <>(body , headers );
114124 this .restOperations .postForEntity (logout , entity , Object .class );
115125 }
116126
117- String computeLogoutEndpoint (HttpServletRequest request ) {
127+ String computeLogoutEndpoint (HttpServletRequest request , OidcBackChannelLogoutAuthentication token ) {
118128 // @formatter:off
119129 UriComponents uriComponents = UriComponentsBuilder
120130 .fromHttpUrl (UrlUtils .buildFullRequestUrl (request ))
@@ -137,6 +147,9 @@ String computeLogoutEndpoint(HttpServletRequest request) {
137147 int port = uriComponents .getPort ();
138148 uriVariables .put ("basePort" , (port == -1 ) ? "" : ":" + port );
139149
150+ String registrationId = token .getClientRegistration ().getRegistrationId ();
151+ uriVariables .put ("registrationId" , registrationId );
152+
140153 return UriComponentsBuilder .fromUriString (this .logoutUri )
141154 .buildAndExpand (uriVariables )
142155 .toUriString ();
@@ -158,34 +171,13 @@ private void handleLogoutFailure(HttpServletResponse response, OAuth2Error error
158171 }
159172 }
160173
161- /**
162- * Use this {@link OidcSessionRegistry} to identify sessions to invalidate. Note that
163- * this class uses
164- * {@link OidcSessionRegistry#removeSessionInformation(OidcLogoutToken)} to identify
165- * sessions.
166- * @param sessionRegistry the {@link OidcSessionRegistry} to use
167- */
168- void setSessionRegistry (OidcSessionRegistry sessionRegistry ) {
169- Assert .notNull (sessionRegistry , "sessionRegistry cannot be null" );
170- this .sessionRegistry = sessionRegistry ;
171- }
172-
173- /**
174- * Use this {@link RestOperations} to perform the per-session back-channel logout
175- * @param restOperations the {@link RestOperations} to use
176- */
177- void setRestOperations (RestOperations restOperations ) {
178- Assert .notNull (restOperations , "restOperations cannot be null" );
179- this .restOperations = restOperations ;
180- }
181-
182174 /**
183175 * Use this logout URI for performing per-session logout. Defaults to {@code /logout}
184176 * since that is the default URI for
185177 * {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
186178 * @param logoutUri the URI to use
187179 */
188- void setLogoutUri (String logoutUri ) {
180+ public void setLogoutUri (String logoutUri ) {
189181 Assert .hasText (logoutUri , "logoutUri cannot be empty" );
190182 this .logoutUri = logoutUri ;
191183 }
@@ -197,7 +189,7 @@ void setLogoutUri(String logoutUri) {
197189 * Note that if you are using Spring Session, this likely needs to change to SESSION.
198190 * @param sessionCookieName the cookie name to use
199191 */
200- void setSessionCookieName (String sessionCookieName ) {
192+ public void setSessionCookieName (String sessionCookieName ) {
201193 Assert .hasText (sessionCookieName , "clientSessionCookieName cannot be empty" );
202194 this .sessionCookieName = sessionCookieName ;
203195 }
0 commit comments