29
29
30
30
import org .springframework .http .HttpEntity ;
31
31
import org .springframework .http .HttpHeaders ;
32
+ import org .springframework .http .MediaType ;
32
33
import org .springframework .http .server .ServletServerHttpResponse ;
33
34
import 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 ;
36
35
import org .springframework .security .oauth2 .client .oidc .session .OidcSessionInformation ;
37
36
import org .springframework .security .oauth2 .client .oidc .session .OidcSessionRegistry ;
38
37
import org .springframework .security .oauth2 .core .OAuth2Error ;
39
38
import org .springframework .security .oauth2 .core .http .converter .OAuth2ErrorHttpMessageConverter ;
40
39
import org .springframework .security .web .authentication .logout .LogoutHandler ;
41
40
import org .springframework .security .web .util .UrlUtils ;
42
41
import org .springframework .util .Assert ;
42
+ import org .springframework .util .LinkedMultiValueMap ;
43
+ import org .springframework .util .MultiValueMap ;
43
44
import org .springframework .web .client .RestClientException ;
44
45
import org .springframework .web .client .RestOperations ;
45
46
import org .springframework .web .client .RestTemplate ;
51
52
* Back-Channel Logout Token and invalidates each one.
52
53
*
53
54
* @author Josh Cummings
54
- * @since 6.2
55
+ * @since 6.4
55
56
* @see <a target="_blank" href=
56
57
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
57
58
* Spec</a>
58
59
*/
59
- final class OidcBackChannelLogoutHandler implements LogoutHandler {
60
+ public final class OidcBackChannelLogoutHandler implements LogoutHandler {
60
61
61
62
private final Log logger = LogFactory .getLog (getClass ());
62
63
63
- private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry () ;
64
+ private final OidcSessionRegistry sessionRegistry ;
64
65
65
66
private RestOperations restOperations = new RestTemplate ();
66
67
67
- private String logoutUri = "{baseScheme}://localhost{basePort}/logout " ;
68
+ private String logoutUri = "{baseUrl}/logout/connect/back-channel/{registrationId} " ;
68
69
69
70
private String sessionCookieName = "JSESSIONID" ;
70
71
71
72
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter ();
72
73
74
+ public OidcBackChannelLogoutHandler (OidcSessionRegistry sessionRegistry ) {
75
+ this .sessionRegistry = sessionRegistry ;
76
+ }
77
+
73
78
@ Override
74
79
public void logout (HttpServletRequest request , HttpServletResponse response , Authentication authentication ) {
75
80
if (!(authentication instanceof OidcBackChannelLogoutAuthentication token )) {
@@ -86,7 +91,7 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut
86
91
for (OidcSessionInformation session : sessions ) {
87
92
totalCount ++;
88
93
try {
89
- eachLogout (request , session );
94
+ eachLogout (request , token , session );
90
95
invalidatedCount ++;
91
96
}
92
97
catch (RestClientException ex ) {
@@ -103,18 +108,23 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut
103
108
}
104
109
}
105
110
106
- private void eachLogout (HttpServletRequest request , OidcSessionInformation session ) {
111
+ private void eachLogout (HttpServletRequest request , OidcBackChannelLogoutAuthentication token ,
112
+ OidcSessionInformation session ) {
107
113
HttpHeaders headers = new HttpHeaders ();
108
114
headers .add (HttpHeaders .COOKIE , this .sessionCookieName + "=" + session .getSessionId ());
109
115
for (Map .Entry <String , String > credential : session .getAuthorities ().entrySet ()) {
110
116
headers .add (credential .getKey (), credential .getValue ());
111
117
}
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 );
114
124
this .restOperations .postForEntity (logout , entity , Object .class );
115
125
}
116
126
117
- String computeLogoutEndpoint (HttpServletRequest request ) {
127
+ String computeLogoutEndpoint (HttpServletRequest request , OidcBackChannelLogoutAuthentication token ) {
118
128
// @formatter:off
119
129
UriComponents uriComponents = UriComponentsBuilder
120
130
.fromHttpUrl (UrlUtils .buildFullRequestUrl (request ))
@@ -137,6 +147,9 @@ String computeLogoutEndpoint(HttpServletRequest request) {
137
147
int port = uriComponents .getPort ();
138
148
uriVariables .put ("basePort" , (port == -1 ) ? "" : ":" + port );
139
149
150
+ String registrationId = token .getClientRegistration ().getRegistrationId ();
151
+ uriVariables .put ("registrationId" , registrationId );
152
+
140
153
return UriComponentsBuilder .fromUriString (this .logoutUri )
141
154
.buildAndExpand (uriVariables )
142
155
.toUriString ();
@@ -158,34 +171,13 @@ private void handleLogoutFailure(HttpServletResponse response, OAuth2Error error
158
171
}
159
172
}
160
173
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
-
182
174
/**
183
175
* Use this logout URI for performing per-session logout. Defaults to {@code /logout}
184
176
* since that is the default URI for
185
177
* {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
186
178
* @param logoutUri the URI to use
187
179
*/
188
- void setLogoutUri (String logoutUri ) {
180
+ public void setLogoutUri (String logoutUri ) {
189
181
Assert .hasText (logoutUri , "logoutUri cannot be empty" );
190
182
this .logoutUri = logoutUri ;
191
183
}
@@ -197,7 +189,7 @@ void setLogoutUri(String logoutUri) {
197
189
* Note that if you are using Spring Session, this likely needs to change to SESSION.
198
190
* @param sessionCookieName the cookie name to use
199
191
*/
200
- void setSessionCookieName (String sessionCookieName ) {
192
+ public void setSessionCookieName (String sessionCookieName ) {
201
193
Assert .hasText (sessionCookieName , "clientSessionCookieName cannot be empty" );
202
194
this .sessionCookieName = sessionCookieName ;
203
195
}
0 commit comments