Skip to content

Commit 25f4e4e

Browse files
committed
Add tests for Oidc Logout implementations
1 parent 1591bd1 commit 25f4e4e

File tree

7 files changed

+979
-12
lines changed

7 files changed

+979
-12
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
import org.springframework.security.core.session.SessionInformation;
2828
import org.springframework.security.core.session.SessionRegistry;
2929
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
30+
import org.springframework.security.oauth2.core.OAuth2Error;
3031
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
32+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
33+
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
3134
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
3235
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
3336
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
@@ -78,7 +81,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
7881
OAuth2Authorization authorization = this.authorizationService.findByToken(
7982
oidcLogoutAuthentication.getIdToken(), ID_TOKEN_TOKEN_TYPE);
8083
if (authorization == null) {
81-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
84+
throwError(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint");
8285
}
8386

8487
RegisteredClient registeredClient = this.registeredClientRepository.findById(
@@ -94,15 +97,15 @@ public Authentication authenticate(Authentication authentication) throws Authent
9497
List<String> audClaim = idToken.getAudience();
9598
if (CollectionUtils.isEmpty(audClaim) ||
9699
!audClaim.contains(registeredClient.getClientId())) {
97-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
100+
throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.AUD);
98101
}
99102
if (StringUtils.hasText(oidcLogoutAuthentication.getClientId()) &&
100103
!oidcLogoutAuthentication.getClientId().equals(registeredClient.getClientId())) {
101-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
104+
throwError(OAuth2ErrorCodes.INVALID_TOKEN, OAuth2ParameterNames.CLIENT_ID);
102105
}
103106
if (StringUtils.hasText(oidcLogoutAuthentication.getPostLogoutRedirectUri()) &&
104107
!registeredClient.getPostLogoutRedirectUris().contains(oidcLogoutAuthentication.getPostLogoutRedirectUri())) {
105-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
108+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri");
106109
}
107110

108111
if (this.logger.isTraceEnabled()) {
@@ -120,7 +123,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
120123
String sidClaim = idToken.getClaim("sid");
121124
if (!StringUtils.hasText(sidClaim) ||
122125
!sidClaim.equals(sessionInformation.getSessionId())) {
123-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
126+
throwError(OAuth2ErrorCodes.INVALID_TOKEN, "sid");
124127
}
125128
}
126129
}
@@ -160,4 +163,12 @@ private static SessionInformation findSessionInformation(Authentication principa
160163
return sessionInformation;
161164
}
162165

166+
private static void throwError(String errorCode, String parameterName) {
167+
OAuth2Error error = new OAuth2Error(
168+
errorCode,
169+
"OpenID Connect 1.0 Logout Request Parameter: " + parameterName,
170+
"https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling");
171+
throw new OAuth2AuthenticationException(error);
172+
}
173+
163174
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,7 @@ private void sendErrorResponse(HttpServletRequest request, HttpServletResponse r
214214
AuthenticationException exception) throws IOException {
215215

216216
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
217-
response.sendError(HttpStatus.BAD_REQUEST.value(),
218-
"OpenID Connect 1.0 RP-Initiated Logout Error: " + error.toString());
217+
response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
219218
}
220219

221220
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.security.core.authority.AuthorityUtils;
2626
import org.springframework.security.core.context.SecurityContextHolder;
2727
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
28+
import org.springframework.security.oauth2.core.OAuth2Error;
2829
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
2930
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
3031
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken;
@@ -56,7 +57,7 @@ public Authentication convert(HttpServletRequest request) {
5657
String idTokenHint = request.getParameter("id_token_hint");
5758
if (!StringUtils.hasText(idTokenHint) ||
5859
request.getParameterValues("id_token_hint").length != 1) {
59-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
60+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "id_token_hint");
6061
}
6162

6263
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
@@ -74,21 +75,21 @@ public Authentication convert(HttpServletRequest request) {
7475
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
7576
if (StringUtils.hasText(clientId) &&
7677
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
77-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
78+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
7879
}
7980

8081
// post_logout_redirect_uri (OPTIONAL)
8182
String postLogoutRedirectUri = parameters.getFirst("post_logout_redirect_uri");
8283
if (StringUtils.hasText(postLogoutRedirectUri) &&
8384
parameters.get("post_logout_redirect_uri").size() != 1) {
84-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
85+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri");
8586
}
8687

8788
// state (OPTIONAL)
8889
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
8990
if (StringUtils.hasText(state) &&
9091
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
91-
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
92+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
9293
}
9394

9495
return new OidcLogoutAuthenticationToken(idTokenHint, principal,
@@ -108,4 +109,12 @@ private static MultiValueMap<String, String> getParameters(HttpServletRequest re
108109
return parameters;
109110
}
110111

112+
private static void throwError(String errorCode, String parameterName) {
113+
OAuth2Error error = new OAuth2Error(
114+
errorCode,
115+
"OpenID Connect 1.0 Logout Request Parameter: " + parameterName,
116+
"https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling");
117+
throw new OAuth2AuthenticationException(error);
118+
}
119+
111120
}

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
4848
import org.springframework.mock.http.client.MockClientHttpResponse;
4949
import org.springframework.mock.web.MockHttpServletResponse;
50+
import org.springframework.mock.web.MockHttpSession;
5051
import org.springframework.security.authentication.TestingAuthenticationToken;
5152
import org.springframework.security.config.Customizer;
5253
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -123,6 +124,7 @@
123124
public class OidcTests {
124125
private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";
125126
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
127+
private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout";
126128
private static final String AUTHORITIES_CLAIM = "authorities";
127129
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
128130
private static EmbeddedDatabase db;
@@ -216,15 +218,69 @@ public void requestWhenAuthenticationRequestThenTokenResponseIncludesIdToken() t
216218
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
217219
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
218220

219-
// Assert user authorities was propagated as claim in ID Token
220221
Jwt idToken = this.jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
222+
223+
// Assert user authorities was propagated as claim in ID Token
221224
List<String> authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM);
222225
Authentication principal = authorization.getAttribute(Principal.class.getName());
223226
Set<String> userAuthorities = new HashSet<>();
224227
for (GrantedAuthority authority : principal.getAuthorities()) {
225228
userAuthorities.add(authority.getAuthority());
226229
}
227230
assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities);
231+
232+
// Assert sid claim was added in ID Token
233+
assertThat(idToken.<String>getClaim("sid")).isNotNull();
234+
}
235+
236+
@Test
237+
public void requestWhenLogoutRequestThenLogout() throws Exception {
238+
this.spring.register(AuthorizationServerConfiguration.class).autowire();
239+
240+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
241+
this.registeredClientRepository.save(registeredClient);
242+
243+
// Login
244+
MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient);
245+
MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
246+
.params(authorizationRequestParameters)
247+
.with(user("user")))
248+
.andExpect(status().is3xxRedirection())
249+
.andReturn();
250+
251+
MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession();
252+
assertThat(session.isNew()).isTrue();
253+
254+
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
255+
String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code");
256+
OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
257+
258+
// Get ID Token
259+
mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
260+
.params(getTokenRequestParameters(registeredClient, authorization))
261+
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
262+
registeredClient.getClientId(), registeredClient.getClientSecret()))
263+
.session(session))
264+
.andExpect(status().isOk())
265+
.andReturn();
266+
267+
MockHttpServletResponse servletResponse = mvcResult.getResponse();
268+
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
269+
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
270+
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
271+
272+
String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
273+
274+
// Logout
275+
mvcResult = this.mvc.perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)
276+
.param("id_token_hint", idToken)
277+
.session(session))
278+
.andExpect(status().is3xxRedirection())
279+
.andReturn();
280+
redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
281+
282+
assertThat(redirectedUrl).matches("/");
283+
assertThat(session.isInvalid()).isTrue();
228284
}
229285

230286
@Test

0 commit comments

Comments
 (0)