Add OpenID Connect 1.0 Logout Endpoint
Closes gh-266
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,8 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
@@ -47,6 +49,7 @@ public class InMemoryOAuth2AuthorizationServiceTests {
|
||||
"code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
|
||||
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
|
||||
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
|
||||
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
|
||||
private InMemoryOAuth2AuthorizationService authorizationService;
|
||||
|
||||
@BeforeEach
|
||||
@@ -263,6 +266,29 @@ public class InMemoryOAuth2AuthorizationServiceTests {
|
||||
assertThat(authorization).isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByTokenWhenIdTokenExistsThenFound() {
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject("subject")
|
||||
.issuedAt(Instant.now().minusSeconds(60))
|
||||
.expiresAt(Instant.now())
|
||||
.build();
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||
.id(ID)
|
||||
.principalName(PRINCIPAL_NAME)
|
||||
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
|
||||
.token(idToken)
|
||||
.build();
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
OAuth2Authorization result = this.authorizationService.findByToken(
|
||||
idToken.getTokenValue(), ID_TOKEN_TOKEN_TYPE);
|
||||
assertThat(authorization).isEqualTo(result);
|
||||
result = this.authorizationService.findByToken(idToken.getTokenValue(), null);
|
||||
assertThat(authorization).isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByTokenWhenRefreshTokenExistsThenFound() {
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,6 +49,7 @@ import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
@@ -76,6 +77,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
|
||||
private static final String OAUTH2_AUTHORIZATION_SCHEMA_CLOB_DATA_TYPE_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql";
|
||||
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
|
||||
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
|
||||
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
|
||||
private static final String ID = "id";
|
||||
private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build();
|
||||
private static final String PRINCIPAL_NAME = "principal";
|
||||
@@ -344,6 +346,32 @@ public class JdbcOAuth2AuthorizationServiceTests {
|
||||
assertThat(authorization).isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByTokenWhenIdTokenExistsThenFound() {
|
||||
when(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
|
||||
.thenReturn(REGISTERED_CLIENT);
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject("subject")
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().truncatedTo(ChronoUnit.MILLIS))
|
||||
.build();
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||
.id(ID)
|
||||
.principalName(PRINCIPAL_NAME)
|
||||
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
|
||||
.token(idToken, (metadata) ->
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
OAuth2Authorization result = this.authorizationService.findByToken(
|
||||
idToken.getTokenValue(), ID_TOKEN_TOKEN_TYPE);
|
||||
assertThat(authorization).isEqualTo(result);
|
||||
result = this.authorizationService.findByToken(idToken.getTokenValue(), null);
|
||||
assertThat(authorization).isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByTokenWhenRefreshTokenExistsThenFound() {
|
||||
when(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
|
||||
@@ -494,7 +522,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
|
||||
|
||||
private static final String PK_FILTER = "id = ?";
|
||||
private static final String UNKNOWN_TOKEN_TYPE_FILTER = "state = ? OR authorizationCodeValue = ? OR " +
|
||||
"accessTokenValue = ? OR refreshTokenValue = ?";
|
||||
"accessTokenValue = ? OR oidcIdTokenValue = ? OR refreshTokenValue = ?";
|
||||
|
||||
// @formatter:off
|
||||
private static final String LOAD_AUTHORIZATION_SQL = "SELECT " + COLUMN_NAMES
|
||||
@@ -539,7 +567,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
|
||||
|
||||
@Override
|
||||
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
|
||||
return findBy(UNKNOWN_TOKEN_TYPE_FILTER, token, token, token, token);
|
||||
return findBy(UNKNOWN_TOKEN_TYPE_FILTER, token, token, token, token, token);
|
||||
}
|
||||
|
||||
private OAuth2Authorization findBy(String filter, Object... args) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,8 +19,11 @@ import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -31,6 +34,8 @@ import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
@@ -95,6 +100,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
private OAuth2TokenGenerator<?> tokenGenerator;
|
||||
private SessionRegistry sessionRegistry;
|
||||
private OAuth2AuthorizationCodeAuthenticationProvider authenticationProvider;
|
||||
|
||||
@BeforeEach
|
||||
@@ -116,8 +122,10 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
return delegatingTokenGenerator.generate(context);
|
||||
}
|
||||
});
|
||||
this.sessionRegistry = mock(SessionRegistry.class);
|
||||
this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||
this.authorizationService, this.tokenGenerator);
|
||||
this.authenticationProvider.setSessionRegistry(this.sessionRegistry);
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
|
||||
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
|
||||
}
|
||||
@@ -146,6 +154,13 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeAuthenticationToken.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSessionRegistryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setSessionRegistry(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("sessionRegistry cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
@@ -456,6 +471,19 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
|
||||
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
|
||||
|
||||
Authentication principal = authorization.getAttribute(Principal.class.getName());
|
||||
|
||||
List<SessionInformation> sessions = new ArrayList<>();
|
||||
sessions.add(new SessionInformation(principal.getPrincipal(),
|
||||
"session3", Date.from(Instant.now())));
|
||||
sessions.add(new SessionInformation(principal.getPrincipal(),
|
||||
"session2", Date.from(Instant.now().minus(1, ChronoUnit.HOURS))));
|
||||
sessions.add(new SessionInformation(principal.getPrincipal(),
|
||||
"session1", Date.from(Instant.now().minus(2, ChronoUnit.HOURS))));
|
||||
SessionInformation expectedSession = sessions.get(0); // Most recent
|
||||
when(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(false)))
|
||||
.thenReturn(sessions);
|
||||
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
@@ -464,7 +492,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
// Access Token context
|
||||
JwtEncodingContext accessTokenContext = jwtEncodingContextCaptor.getAllValues().get(0);
|
||||
assertThat(accessTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(accessTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(Principal.class.getName()));
|
||||
assertThat(accessTokenContext.<Authentication>getPrincipal()).isEqualTo(principal);
|
||||
assertThat(accessTokenContext.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(accessTokenContext.getAuthorization().getAccessToken()).isNull();
|
||||
assertThat(accessTokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes());
|
||||
@@ -480,13 +508,15 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
// ID Token context
|
||||
JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1);
|
||||
assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(idTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(Principal.class.getName()));
|
||||
assertThat(idTokenContext.<Authentication>getPrincipal()).isEqualTo(principal);
|
||||
assertThat(idTokenContext.getAuthorization()).isNotEqualTo(authorization);
|
||||
assertThat(idTokenContext.getAuthorization().getAccessToken()).isNotNull();
|
||||
assertThat(idTokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes());
|
||||
assertThat(idTokenContext.getTokenType().getValue()).isEqualTo(OidcParameterNames.ID_TOKEN);
|
||||
assertThat(idTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(idTokenContext.<OAuth2AuthorizationGrantAuthenticationToken>getAuthorizationGrant()).isEqualTo(authentication);
|
||||
SessionInformation sessionInformation = idTokenContext.get(SessionInformation.class);
|
||||
assertThat(sessionInformation).isNotNull().isSameAs(expectedSession);
|
||||
assertThat(idTokenContext.getJwsHeader()).isNotNull();
|
||||
assertThat(idTokenContext.getClaims()).isNotNull();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -294,6 +294,7 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
+ "clientAuthenticationMethods, "
|
||||
+ "authorizationGrantTypes, "
|
||||
+ "redirectUris, "
|
||||
+ "postLogoutRedirectUris, "
|
||||
+ "scopes, "
|
||||
+ "clientSettings,"
|
||||
+ "tokenSettings";
|
||||
@@ -305,7 +306,7 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
|
||||
// @formatter:off
|
||||
private static final String INSERT_REGISTERED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME
|
||||
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
// @formatter:on
|
||||
|
||||
private CustomJdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
|
||||
@@ -353,6 +354,7 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(rs.getString("clientAuthenticationMethods"));
|
||||
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(rs.getString("authorizationGrantTypes"));
|
||||
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirectUris"));
|
||||
Set<String> postLogoutRedirectUris = StringUtils.commaDelimitedListToSet(rs.getString("postLogoutRedirectUris"));
|
||||
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes"));
|
||||
|
||||
// @formatter:off
|
||||
@@ -369,6 +371,7 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
authorizationGrantTypes.forEach(grantType ->
|
||||
grantTypes.add(resolveAuthorizationGrantType(grantType))))
|
||||
.redirectUris((uris) -> uris.addAll(redirectUris))
|
||||
.postLogoutRedirectUris((uris) -> uris.addAll(postLogoutRedirectUris))
|
||||
.scopes((scopes) -> scopes.addAll(clientScopes));
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,6 +40,7 @@ public class RegisteredClientTests {
|
||||
private static final String CLIENT_ID = "client-1";
|
||||
private static final String CLIENT_SECRET = "secret";
|
||||
private static final Set<String> REDIRECT_URIS = Collections.singleton("https://example.com");
|
||||
private static final Set<String> POST_LOGOUT_REDIRECT_URIS = Collections.singleton("https://example.com/oidc-post-logout");
|
||||
private static final Set<String> SCOPES = Collections.unmodifiableSet(
|
||||
Stream.of("openid", "profile", "email").collect(Collectors.toSet()));
|
||||
private static final Set<ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHODS =
|
||||
@@ -71,6 +72,7 @@ public class RegisteredClientTests {
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.redirectUris(redirectUris -> redirectUris.addAll(REDIRECT_URIS))
|
||||
.postLogoutRedirectUris(postLogoutRedirectUris -> postLogoutRedirectUris.addAll(POST_LOGOUT_REDIRECT_URIS))
|
||||
.scopes(scopes -> scopes.addAll(SCOPES))
|
||||
.build();
|
||||
|
||||
@@ -84,6 +86,7 @@ public class RegisteredClientTests {
|
||||
.isEqualTo(Collections.singleton(AuthorizationGrantType.AUTHORIZATION_CODE));
|
||||
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(CLIENT_AUTHENTICATION_METHODS);
|
||||
assertThat(registration.getRedirectUris()).isEqualTo(REDIRECT_URIS);
|
||||
assertThat(registration.getPostLogoutRedirectUris()).isEqualTo(POST_LOGOUT_REDIRECT_URIS);
|
||||
assertThat(registration.getScopes()).isEqualTo(SCOPES);
|
||||
}
|
||||
|
||||
@@ -229,6 +232,35 @@ public class RegisteredClientTests {
|
||||
).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPostLogoutRedirectUriInvalidThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() ->
|
||||
RegisteredClient.withId(ID)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.redirectUris(redirectUris -> redirectUris.addAll(REDIRECT_URIS))
|
||||
.postLogoutRedirectUri("invalid URI")
|
||||
.build()
|
||||
).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPostLogoutRedirectUriContainsFragmentThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() ->
|
||||
RegisteredClient.withId(ID)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.redirectUri("https://example.com")
|
||||
.postLogoutRedirectUri("https://example.com/index#fragment")
|
||||
.scopes(scopes -> scopes.addAll(SCOPES))
|
||||
.build()
|
||||
).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTwoAuthorizationGrantTypesAreProvidedThenBothAreRegistered() {
|
||||
RegisteredClient registration = RegisteredClient.withId(ID)
|
||||
@@ -345,6 +377,8 @@ public class RegisteredClientTests {
|
||||
assertThat(registration.getAuthorizationGrantTypes()).isNotSameAs(updated.getAuthorizationGrantTypes());
|
||||
assertThat(registration.getRedirectUris()).isEqualTo(updated.getRedirectUris());
|
||||
assertThat(registration.getRedirectUris()).isNotSameAs(updated.getRedirectUris());
|
||||
assertThat(registration.getPostLogoutRedirectUris()).isEqualTo(updated.getPostLogoutRedirectUris());
|
||||
assertThat(registration.getPostLogoutRedirectUris()).isNotSameAs(updated.getPostLogoutRedirectUris());
|
||||
assertThat(registration.getScopes()).isEqualTo(updated.getScopes());
|
||||
assertThat(registration.getScopes()).isNotSameAs(updated.getScopes());
|
||||
assertThat(registration.getClientSettings()).isEqualTo(updated.getClientSettings());
|
||||
@@ -360,6 +394,7 @@ public class RegisteredClientTests {
|
||||
String newSecret = "new-secret";
|
||||
String newScope = "new-scope";
|
||||
String newRedirectUri = "https://another-redirect-uri.com";
|
||||
String newPostLogoutRedirectUri = "https://another-post-logout-redirect-uri.com";
|
||||
RegisteredClient updated = RegisteredClient.from(registration)
|
||||
.clientName(newName)
|
||||
.clientSecret(newSecret)
|
||||
@@ -371,6 +406,10 @@ public class RegisteredClientTests {
|
||||
redirectUris.clear();
|
||||
redirectUris.add(newRedirectUri);
|
||||
})
|
||||
.postLogoutRedirectUris(postLogoutRedirectUris -> {
|
||||
postLogoutRedirectUris.clear();
|
||||
postLogoutRedirectUris.add(newPostLogoutRedirectUri);
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(registration.getClientName()).isNotEqualTo(newName);
|
||||
@@ -381,6 +420,8 @@ public class RegisteredClientTests {
|
||||
assertThat(updated.getScopes()).containsExactly(newScope);
|
||||
assertThat(registration.getRedirectUris()).doesNotContain(newRedirectUri);
|
||||
assertThat(updated.getRedirectUris()).containsExactly(newRedirectUri);
|
||||
assertThat(registration.getPostLogoutRedirectUris()).doesNotContain(newPostLogoutRedirectUri);
|
||||
assertThat(updated.getPostLogoutRedirectUris()).containsExactly(newPostLogoutRedirectUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -38,6 +38,7 @@ public class TestRegisteredClients {
|
||||
.redirectUri("https://example.com/callback-1")
|
||||
.redirectUri("https://example.com/callback-2")
|
||||
.redirectUri("https://example.com/callback-3")
|
||||
.postLogoutRedirectUri("https://example.com/oidc-post-logout")
|
||||
.scope("scope1");
|
||||
}
|
||||
|
||||
@@ -52,6 +53,7 @@ public class TestRegisteredClients {
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
|
||||
.redirectUri("https://example.com")
|
||||
.postLogoutRedirectUri("https://example.com/oidc-post-logout")
|
||||
.scope("scope1")
|
||||
.scope("scope2");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -126,6 +126,7 @@ public class OidcProviderConfigurationTests {
|
||||
jsonPath("$.token_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()),
|
||||
jsonPath("jwks_uri").value(ISSUER_URL.concat(this.authorizationServerSettings.getJwkSetEndpoint())),
|
||||
jsonPath("userinfo_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getOidcUserInfoEndpoint())),
|
||||
jsonPath("end_session_endpoint").value(ISSUER_URL.concat(this.authorizationServerSettings.getOidcLogoutEndpoint())),
|
||||
jsonPath("response_types_supported").value(OAuth2AuthorizationResponseType.CODE.getValue()),
|
||||
jsonPath("$.grant_types_supported[0]").value(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
|
||||
jsonPath("$.grant_types_supported[1]").value(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()),
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
@@ -123,6 +124,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
public class OidcTests {
|
||||
private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";
|
||||
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
|
||||
private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout";
|
||||
private static final String AUTHORITIES_CLAIM = "authorities";
|
||||
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
|
||||
private static EmbeddedDatabase db;
|
||||
@@ -216,8 +218,9 @@ public class OidcTests {
|
||||
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
|
||||
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
|
||||
// Assert user authorities was propagated as claim in ID Token
|
||||
Jwt idToken = this.jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
|
||||
|
||||
// Assert user authorities was propagated as claim in ID Token
|
||||
List<String> authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM);
|
||||
Authentication principal = authorization.getAttribute(Principal.class.getName());
|
||||
Set<String> userAuthorities = new HashSet<>();
|
||||
@@ -225,6 +228,59 @@ public class OidcTests {
|
||||
userAuthorities.add(authority.getAuthority());
|
||||
}
|
||||
assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities);
|
||||
|
||||
// Assert sid claim was added in ID Token
|
||||
assertThat(idToken.<String>getClaim("sid")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenLogoutRequestThenLogout() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
// Login
|
||||
MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient);
|
||||
MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.params(authorizationRequestParameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession();
|
||||
assertThat(session.isNew()).isTrue();
|
||||
|
||||
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code");
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
|
||||
// Get ID Token
|
||||
mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret()))
|
||||
.session(session))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
|
||||
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
|
||||
String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
||||
|
||||
// Logout
|
||||
mvcResult = this.mvc.perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)
|
||||
.param("id_token_hint", idToken)
|
||||
.session(session))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
|
||||
assertThat(redirectedUrl).matches("/");
|
||||
assertThat(session.isInvalid()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -58,6 +58,7 @@ public class OidcClientRegistrationTests {
|
||||
.clientSecretExpiresAt(clientSecretExpiresAt)
|
||||
.clientName("client-name")
|
||||
.redirectUri("https://client.example.com")
|
||||
.postLogoutRedirectUri("https://client.example.com/oidc-post-logout")
|
||||
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue())
|
||||
.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256.getName())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
@@ -79,6 +80,7 @@ public class OidcClientRegistrationTests {
|
||||
assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt);
|
||||
assertThat(clientRegistration.getClientName()).isEqualTo("client-name");
|
||||
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
|
||||
assertThat(clientRegistration.getPostLogoutRedirectUris()).containsOnly("https://client.example.com/oidc-post-logout");
|
||||
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue());
|
||||
assertThat(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()).isEqualTo(MacAlgorithm.HS256.getName());
|
||||
assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
@@ -108,6 +110,7 @@ public class OidcClientRegistrationTests {
|
||||
claims.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt);
|
||||
claims.put(OidcClientMetadataClaimNames.CLIENT_NAME, "client-name");
|
||||
claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, Collections.singletonList("https://client.example.com"));
|
||||
claims.put(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, Collections.singletonList("https://client.example.com/oidc-post-logout"));
|
||||
claims.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue());
|
||||
claims.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, MacAlgorithm.HS256.getName());
|
||||
claims.put(OidcClientMetadataClaimNames.GRANT_TYPES, Arrays.asList(
|
||||
@@ -128,6 +131,7 @@ public class OidcClientRegistrationTests {
|
||||
assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt);
|
||||
assertThat(clientRegistration.getClientName()).isEqualTo("client-name");
|
||||
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
|
||||
assertThat(clientRegistration.getPostLogoutRedirectUris()).containsOnly("https://client.example.com/oidc-post-logout");
|
||||
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue());
|
||||
assertThat(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()).isEqualTo(MacAlgorithm.HS256.getName());
|
||||
assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
@@ -261,6 +265,41 @@ public class OidcClientRegistrationTests {
|
||||
assertThat(clientRegistration.getRedirectUris()).containsExactly("https://client2.example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPostLogoutRedirectUrisNotListThenThrowIllegalArgumentException() {
|
||||
OidcClientRegistration.Builder builder = this.minimalBuilder
|
||||
.claim(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, "postLogoutRedirectUris");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("post_logout_redirect_uris must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPostLogoutRedirectUrisEmptyListThenThrowIllegalArgumentException() {
|
||||
OidcClientRegistration.Builder builder = this.minimalBuilder
|
||||
.claim(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, Collections.emptyList());
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("post_logout_redirect_uris cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPostLogoutRedirectUrisAddingOrRemovingThenCorrectValues() {
|
||||
// @formatter:off
|
||||
OidcClientRegistration clientRegistration = this.minimalBuilder
|
||||
.postLogoutRedirectUri("https://client1.example.com/oidc-post-logout")
|
||||
.postLogoutRedirectUris(postLogoutRedirectUris -> {
|
||||
postLogoutRedirectUris.clear();
|
||||
postLogoutRedirectUris.add("https://client2.example.com/oidc-post-logout");
|
||||
})
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(clientRegistration.getPostLogoutRedirectUris()).containsExactly("https://client2.example.com/oidc-post-logout");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenGrantTypesNotListThenThrowIllegalArgumentException() {
|
||||
OidcClientRegistration.Builder builder = this.minimalBuilder
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,6 +62,7 @@ public class OidcProviderConfigurationTests {
|
||||
.userInfoEndpoint("https://example.com/issuer1/userinfo")
|
||||
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.clientRegistrationEndpoint("https://example.com/issuer1/connect/register")
|
||||
.endSessionEndpoint("https://example.com/issuer1/connect/logout")
|
||||
.claim("a-claim", "a-value")
|
||||
.build();
|
||||
|
||||
@@ -77,6 +78,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(url("https://example.com/issuer1/userinfo"));
|
||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());
|
||||
assertThat(providerConfiguration.getClientRegistrationEndpoint()).isEqualTo(url("https://example.com/issuer1/connect/register"));
|
||||
assertThat(providerConfiguration.getEndSessionEndpoint()).isEqualTo(url("https://example.com/issuer1/connect/logout"));
|
||||
assertThat(providerConfiguration.<String>getClaim("a-claim")).isEqualTo("a-value");
|
||||
}
|
||||
|
||||
@@ -118,6 +120,7 @@ public class OidcProviderConfigurationTests {
|
||||
claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, Collections.singletonList("RS256"));
|
||||
claims.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, "https://example.com/issuer1/userinfo");
|
||||
claims.put(OidcProviderMetadataClaimNames.REGISTRATION_ENDPOINT, "https://example.com/issuer1/connect/register");
|
||||
claims.put(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, "https://example.com/issuer1/connect/logout");
|
||||
claims.put("some-claim", "some-value");
|
||||
|
||||
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
|
||||
@@ -134,6 +137,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(url("https://example.com/issuer1/userinfo"));
|
||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(providerConfiguration.getClientRegistrationEndpoint()).isEqualTo(url("https://example.com/issuer1/connect/register"));
|
||||
assertThat(providerConfiguration.getEndSessionEndpoint()).isEqualTo(url("https://example.com/issuer1/connect/logout"));
|
||||
assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
@@ -150,6 +154,7 @@ public class OidcProviderConfigurationTests {
|
||||
claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, Collections.singletonList("RS256"));
|
||||
claims.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, url("https://example.com/issuer1/userinfo"));
|
||||
claims.put(OidcProviderMetadataClaimNames.REGISTRATION_ENDPOINT, url("https://example.com/issuer1/connect/register"));
|
||||
claims.put(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, url("https://example.com/issuer1/connect/logout"));
|
||||
claims.put("some-claim", "some-value");
|
||||
|
||||
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
|
||||
@@ -166,6 +171,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(url("https://example.com/issuer1/userinfo"));
|
||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(providerConfiguration.getClientRegistrationEndpoint()).isEqualTo(url("https://example.com/issuer1/connect/register"));
|
||||
assertThat(providerConfiguration.getEndSessionEndpoint()).isEqualTo(url("https://example.com/issuer1/connect/logout"));
|
||||
assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
@@ -412,6 +418,16 @@ public class OidcProviderConfigurationTests {
|
||||
.withMessage("clientRegistrationEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenEndSessionEndpointNotUrlThenThrowIllegalArgumentException() {
|
||||
OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("endSessionEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseTypesWhenAddingOrRemovingThenCorrectValues() {
|
||||
OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -359,6 +359,78 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
||||
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidPostLogoutRedirectUriThenThrowOAuth2AuthenticationException() {
|
||||
Jwt jwt = createJwtClientRegistration();
|
||||
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
jwt.getTokenValue(), jwt.getIssuedAt(),
|
||||
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||
registeredClient, jwtAccessToken, jwt.getClaims()).build();
|
||||
when(this.authorizationService.findByToken(
|
||||
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
JwtAuthenticationToken principal = new JwtAuthenticationToken(
|
||||
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
|
||||
// @formatter:off
|
||||
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
|
||||
.redirectUri("https://client.example.com")
|
||||
.postLogoutRedirectUri("invalid uri")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
|
||||
principal, clientRegistration);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo("invalid_client_metadata");
|
||||
assertThat(error.getDescription()).contains(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS);
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenPostLogoutRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
|
||||
Jwt jwt = createJwtClientRegistration();
|
||||
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
jwt.getTokenValue(), jwt.getIssuedAt(),
|
||||
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||
registeredClient, jwtAccessToken, jwt.getClaims()).build();
|
||||
when(this.authorizationService.findByToken(
|
||||
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
JwtAuthenticationToken principal = new JwtAuthenticationToken(
|
||||
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
|
||||
// @formatter:off
|
||||
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
|
||||
.redirectUri("https://client.example.com")
|
||||
.postLogoutRedirectUri("https://client.example.com/oidc-post-logout#fragment")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
|
||||
principal, clientRegistration);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo("invalid_client_metadata");
|
||||
assertThat(error.getDescription()).contains(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS);
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidTokenEndpointAuthenticationMethodThenThrowOAuth2AuthenticationException() {
|
||||
Jwt jwt = createJwtClientRegistration();
|
||||
@@ -545,6 +617,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
||||
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
|
||||
.clientName("client-name")
|
||||
.redirectUri("https://client.example.com")
|
||||
.postLogoutRedirectUri("https://client.example.com/oidc-post-logout")
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.scope("scope1")
|
||||
@@ -588,6 +661,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
||||
assertThat(registeredClientResult.getClientName()).isEqualTo(clientRegistration.getClientName());
|
||||
assertThat(registeredClientResult.getClientAuthenticationMethods()).containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
assertThat(registeredClientResult.getRedirectUris()).containsExactly("https://client.example.com");
|
||||
assertThat(registeredClientResult.getPostLogoutRedirectUris()).containsExactly("https://client.example.com/oidc-post-logout");
|
||||
assertThat(registeredClientResult.getAuthorizationGrantTypes())
|
||||
.containsExactlyInAnyOrder(AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS);
|
||||
assertThat(registeredClientResult.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
|
||||
@@ -603,6 +677,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
||||
assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClientResult.getClientName());
|
||||
assertThat(clientRegistrationResult.getRedirectUris())
|
||||
.containsExactlyInAnyOrderElementsOf(registeredClientResult.getRedirectUris());
|
||||
assertThat(clientRegistrationResult.getPostLogoutRedirectUris())
|
||||
.containsExactlyInAnyOrderElementsOf(registeredClientResult.getPostLogoutRedirectUris());
|
||||
|
||||
List<String> grantTypes = new ArrayList<>();
|
||||
registeredClientResult.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link OidcLogoutAuthenticationProvider}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OidcLogoutAuthenticationProviderTests {
|
||||
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private SessionRegistry sessionRegistry;
|
||||
private AuthorizationServerSettings authorizationServerSettings;
|
||||
private OidcLogoutAuthenticationProvider authenticationProvider;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
this.sessionRegistry = mock(SessionRegistry.class);
|
||||
this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
|
||||
TestAuthorizationServerContext authorizationServerContext =
|
||||
new TestAuthorizationServerContext(this.authorizationServerSettings, null);
|
||||
AuthorizationServerContextHolder.setContext(authorizationServerContext);
|
||||
this.authenticationProvider = new OidcLogoutAuthenticationProvider(
|
||||
this.registeredClientRepository, this.authorizationService, this.sessionRegistry);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
AuthorizationServerContextHolder.resetContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationProvider(null, this.authorizationService, this.sessionRegistry))
|
||||
.withMessage("registeredClientRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationProvider(this.registeredClientRepository, null, this.sessionRegistry))
|
||||
.withMessage("authorizationService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenSessionRegistryNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationProvider(this.registeredClientRepository, this.authorizationService, null))
|
||||
.withMessage("sessionRegistry cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsWhenTypeOidcLogoutAuthenticationTokenThenReturnTrue() {
|
||||
assertThat(this.authenticationProvider.supports(OidcLogoutAuthenticationToken.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenIdTokenNotFoundThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
"id-token", principal, "session-1", null, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
assertThat(error.getDescription()).contains("id_token_hint");
|
||||
});
|
||||
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenMissingAudienceThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, "session-1", null, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
assertThat(error.getDescription()).contains(IdTokenClaimNames.AUD);
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidAudienceThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.audience(Collections.singleton(registeredClient.getClientId() + "-invalid"))
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, "session-1", null, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
assertThat(error.getDescription()).contains(IdTokenClaimNames.AUD);
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.audience(Collections.singleton(registeredClient.getClientId()))
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, "session-1", registeredClient.getClientId() + "-invalid", null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID);
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidPostLogoutRedirectUriThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.audience(Collections.singleton(registeredClient.getClientId()))
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, "session-1", registeredClient.getClientId(),
|
||||
"https://example.com/callback-1-invalid", null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
assertThat(error.getDescription()).contains("post_logout_redirect_uri");
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenMissingSidThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.audience(Collections.singleton(registeredClient.getClientId()))
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
String sessionId = "session-1";
|
||||
List<SessionInformation> sessions = Collections.singletonList(
|
||||
new SessionInformation(principal.getPrincipal(), sessionId, Date.from(Instant.now())));
|
||||
when(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(true)))
|
||||
.thenReturn(sessions);
|
||||
|
||||
principal.setAuthenticated(true);
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, sessionId, null, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
assertThat(error.getDescription()).contains("sid");
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidSidThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.audience(Collections.singleton(registeredClient.getClientId()))
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.claim("sid", "other-session")
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
String sessionId = "session-1";
|
||||
List<SessionInformation> sessions = Collections.singletonList(
|
||||
new SessionInformation(principal.getPrincipal(), sessionId, Date.from(Instant.now())));
|
||||
when(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(true)))
|
||||
.thenReturn(sessions);
|
||||
|
||||
principal.setAuthenticated(true);
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, sessionId, null, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
assertThat(error.getDescription()).contains("sid");
|
||||
});
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenValidIdTokenThenAuthenticated() {
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
String sessionId = "session-1";
|
||||
OidcIdToken idToken = OidcIdToken.withTokenValue("id-token")
|
||||
.issuer("https://provider.com")
|
||||
.subject(principal.getName())
|
||||
.audience(Collections.singleton(registeredClient.getClientId()))
|
||||
.issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS))
|
||||
.claim("sid", sessionId)
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.token(idToken,
|
||||
(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
SessionInformation sessionInformation = new SessionInformation(
|
||||
principal.getPrincipal(), sessionId, Date.from(Instant.now()));
|
||||
List<SessionInformation> sessions = Collections.singletonList(sessionInformation);
|
||||
when(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(true)))
|
||||
.thenReturn(sessions);
|
||||
|
||||
principal.setAuthenticated(true);
|
||||
String postLogoutRedirectUri = registeredClient.getPostLogoutRedirectUris().toArray(new String[0])[0];
|
||||
String state = "state";
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
idToken.getTokenValue(), principal, sessionId, registeredClient.getClientId(), postLogoutRedirectUri, state);
|
||||
|
||||
OidcLogoutAuthenticationToken authenticationResult =
|
||||
(OidcLogoutAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
|
||||
verify(this.registeredClientRepository).findById(
|
||||
eq(authorization.getRegisteredClientId()));
|
||||
|
||||
assertThat(authenticationResult.getPrincipal()).isEqualTo(principal);
|
||||
assertThat(authenticationResult.getCredentials().toString()).isEmpty();
|
||||
assertThat(authenticationResult.getIdToken()).isEqualTo(idToken.getTokenValue());
|
||||
assertThat(authenticationResult.getSessionId()).isEqualTo(sessionInformation.getSessionId());
|
||||
assertThat(authenticationResult.getSessionInformation()).isEqualTo(sessionInformation);
|
||||
assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authenticationResult.getPostLogoutRedirectUri()).isEqualTo(postLogoutRedirectUri);
|
||||
assertThat(authenticationResult.getState()).isEqualTo(state);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OidcLogoutAuthenticationToken}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OidcLogoutAuthenticationTokenTests {
|
||||
private final String idToken = "id-token";
|
||||
private final TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
private final String sessionId = "session-1";
|
||||
private final SessionInformation sessionInformation = new SessionInformation(this.principal, "session-2", Date.from(Instant.now()));
|
||||
private final String clientId = "client-1";
|
||||
private final String postLogoutRedirectUri = "https://example.com/oidc-post-logout";
|
||||
private final String state = "state-1";
|
||||
|
||||
@Test
|
||||
public void constructorWhenIdTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
null, this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("idToken cannot be empty");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
null, this.principal, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("idToken cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenIdTokenEmptyThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
"", this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("idToken cannot be empty");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
"", this.principal, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("idToken cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
this.idToken, null, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("principal cannot be null");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
this.idToken, null, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("principal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenSessionIdProvidedThenCreated() {
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
this.idToken, this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.principal);
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.getIdToken()).isEqualTo(this.idToken);
|
||||
assertThat(authentication.getSessionId()).isEqualTo(this.sessionId);
|
||||
assertThat(authentication.getSessionInformation()).isNull();
|
||||
assertThat(authentication.getClientId()).isEqualTo(this.clientId);
|
||||
assertThat(authentication.getPostLogoutRedirectUri()).isEqualTo(this.postLogoutRedirectUri);
|
||||
assertThat(authentication.getState()).isEqualTo(this.state);
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenSessionInformationProvidedThenCreated() {
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
this.idToken, this.principal, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.principal);
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.getIdToken()).isEqualTo(this.idToken);
|
||||
assertThat(authentication.getSessionId()).isEqualTo(this.sessionInformation.getSessionId());
|
||||
assertThat(authentication.getSessionInformation()).isEqualTo(this.sessionInformation);
|
||||
assertThat(authentication.getClientId()).isEqualTo(this.clientId);
|
||||
assertThat(authentication.getPostLogoutRedirectUri()).isEqualTo(this.postLogoutRedirectUri);
|
||||
assertThat(authentication.getState()).isEqualTo(this.state);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -99,6 +99,9 @@ public class OidcClientRegistrationHttpMessageConverterTests {
|
||||
+" \"redirect_uris\": [\n"
|
||||
+ " \"https://client.example.com\"\n"
|
||||
+ " ],\n"
|
||||
+" \"post_logout_redirect_uris\": [\n"
|
||||
+ " \"https://client.example.com/oidc-post-logout\"\n"
|
||||
+ " ],\n"
|
||||
+" \"token_endpoint_auth_method\": \"client_secret_jwt\",\n"
|
||||
+" \"token_endpoint_auth_signing_alg\": \"HS256\",\n"
|
||||
+" \"grant_types\": [\n"
|
||||
@@ -125,6 +128,7 @@ public class OidcClientRegistrationHttpMessageConverterTests {
|
||||
assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(Instant.ofEpochSecond(1607637467L));
|
||||
assertThat(clientRegistration.getClientName()).isEqualTo("client-name");
|
||||
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
|
||||
assertThat(clientRegistration.getPostLogoutRedirectUris()).containsOnly("https://client.example.com/oidc-post-logout");
|
||||
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue());
|
||||
assertThat(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()).isEqualTo(MacAlgorithm.HS256.getName());
|
||||
assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
@@ -183,6 +187,7 @@ public class OidcClientRegistrationHttpMessageConverterTests {
|
||||
.clientSecretExpiresAt(Instant.ofEpochSecond(1607637467))
|
||||
.clientName("client-name")
|
||||
.redirectUri("https://client.example.com")
|
||||
.postLogoutRedirectUri("https://client.example.com/oidc-post-logout")
|
||||
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue())
|
||||
.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256.getName())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
@@ -208,6 +213,7 @@ public class OidcClientRegistrationHttpMessageConverterTests {
|
||||
assertThat(clientRegistrationResponse).contains("\"client_secret_expires_at\":1607637467");
|
||||
assertThat(clientRegistrationResponse).contains("\"client_name\":\"client-name\"");
|
||||
assertThat(clientRegistrationResponse).contains("\"redirect_uris\":[\"https://client.example.com\"]");
|
||||
assertThat(clientRegistrationResponse).contains("\"post_logout_redirect_uris\":[\"https://client.example.com/oidc-post-logout\"]");
|
||||
assertThat(clientRegistrationResponse).contains("\"token_endpoint_auth_method\":\"client_secret_jwt\"");
|
||||
assertThat(clientRegistrationResponse).contains("\"token_endpoint_auth_signing_alg\":\"HS256\"");
|
||||
assertThat(clientRegistrationResponse).contains("\"grant_types\":[\"authorization_code\",\"client_credentials\"]");
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.oidc.web;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link OidcLogoutEndpointFilter}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OidcLogoutEndpointFilterTests {
|
||||
private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout";
|
||||
private AuthenticationManager authenticationManager;
|
||||
private OidcLogoutEndpointFilter filter;
|
||||
private TestingAuthenticationToken principal;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.authenticationManager = mock(AuthenticationManager.class);
|
||||
this.filter = new OidcLogoutEndpointFilter(this.authenticationManager);
|
||||
this.principal = new TestingAuthenticationToken("principal", "credentials");
|
||||
this.principal.setAuthenticated(true);
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(this.principal);
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OidcLogoutEndpointFilter(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationManager cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenLogoutEndpointUriNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OidcLogoutEndpointFilter(this.authenticationManager, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("logoutEndpointUri cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationSuccessHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationFailureHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotLogoutRequestThenNotProcessed() throws Exception {
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestMissingIdTokenHintThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenRequestInvalidParameterThenError(
|
||||
createLogoutRequest(TestRegisteredClients.registeredClient().build()),
|
||||
"id_token_hint",
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.removeParameter("id_token_hint"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestMultipleIdTokenHintThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenRequestInvalidParameterThenError(
|
||||
createLogoutRequest(TestRegisteredClients.registeredClient().build()),
|
||||
"id_token_hint",
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.addParameter("id_token_hint", "id-token-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestMultipleClientIdThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenRequestInvalidParameterThenError(
|
||||
createLogoutRequest(TestRegisteredClients.registeredClient().build()),
|
||||
OAuth2ParameterNames.CLIENT_ID,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestMultiplePostLogoutRedirectUriThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenRequestInvalidParameterThenError(
|
||||
createLogoutRequest(TestRegisteredClients.registeredClient().build()),
|
||||
"post_logout_redirect_uri",
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.addParameter("post_logout_redirect_uri", "https://example.com/callback-4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestMultipleStateThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenRequestInvalidParameterThenError(
|
||||
createLogoutRequest(TestRegisteredClients.registeredClient().build()),
|
||||
OAuth2ParameterNames.STATE,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.addParameter(OAuth2ParameterNames.STATE, "state-2"));
|
||||
}
|
||||
|
||||
private void doFilterWhenRequestInvalidParameterThenError(MockHttpServletRequest request,
|
||||
String parameterName, String errorCode, Consumer<MockHttpServletRequest> requestConsumer) throws Exception {
|
||||
|
||||
requestConsumer.accept(request);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
|
||||
assertThat(response.getErrorMessage()).isEqualTo("[" + errorCode + "] OpenID Connect 1.0 Logout Request Parameter: " + parameterName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestAuthenticationExceptionThenErrorResponse() throws Exception {
|
||||
OAuth2Error error = new OAuth2Error("errorCode", "errorDescription", "errorUri");
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenThrow(new OAuth2AuthenticationException(error));
|
||||
|
||||
MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
|
||||
assertThat(response.getErrorMessage()).isEqualTo(error.toString());
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.principal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
"id-token", this.principal, (SessionInformation) null, null, null, null);
|
||||
|
||||
AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
|
||||
when(authenticationConverter.convert(any())).thenReturn(authentication);
|
||||
this.filter.setAuthenticationConverter(authenticationConverter);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authentication);
|
||||
|
||||
MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(authenticationConverter).convert(any());
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verifyNoInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception {
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
"id-token", this.principal, (SessionInformation) null, null, null, null);
|
||||
|
||||
AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
|
||||
this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authentication);
|
||||
|
||||
MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), same(authentication));
|
||||
verifyNoInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception {
|
||||
AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class);
|
||||
this.filter.setAuthenticationFailureHandler(authenticationFailureHandler);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenThrow(new AuthenticationServiceException("AuthenticationServiceException"));
|
||||
|
||||
MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class);
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), authenticationExceptionCaptor.capture());
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(authenticationExceptionCaptor.getValue())
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
assertThat(error.getDescription()).contains("AuthenticationServiceException");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestAuthenticatedThenLogout() throws Exception {
|
||||
MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build());
|
||||
MockHttpSession session = (MockHttpSession) request.getSession(true);
|
||||
|
||||
SessionInformation sessionInformation = new SessionInformation(
|
||||
this.principal, session.getId(), Date.from(Instant.now()));
|
||||
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
"id-token", this.principal, sessionInformation, null, null, null);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authentication);
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/");
|
||||
assertThat(session.isInvalid()).isTrue();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenLogoutRequestAuthenticatedWithPostLogoutRedirectUriThenPostLogoutRedirect() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
MockHttpServletRequest request = createLogoutRequest(registeredClient);
|
||||
MockHttpSession session = (MockHttpSession) request.getSession(true);
|
||||
|
||||
SessionInformation sessionInformation = new SessionInformation(
|
||||
this.principal, session.getId(), Date.from(Instant.now()));
|
||||
|
||||
String postLogoutRedirectUri = registeredClient.getPostLogoutRedirectUris().iterator().next();
|
||||
String state = "state-1";
|
||||
OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
|
||||
"id-token", this.principal, sessionInformation,
|
||||
registeredClient.getClientId(), postLogoutRedirectUri, state);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authentication);
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo(postLogoutRedirectUri + "?state=" + state);
|
||||
assertThat(session.isInvalid()).isTrue();
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
|
||||
}
|
||||
|
||||
private static MockHttpServletRequest createLogoutRequest(RegisteredClient registeredClient) {
|
||||
String requestUri = DEFAULT_OIDC_LOGOUT_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
|
||||
request.addParameter("id_token_hint", "id-token");
|
||||
request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
request.addParameter("post_logout_redirect_uri", registeredClient.getPostLogoutRedirectUris().iterator().next());
|
||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -92,6 +92,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
String tokenEndpoint = "/oauth2/v1/token";
|
||||
String jwkSetEndpoint = "/oauth2/v1/jwks";
|
||||
String userInfoEndpoint = "/userinfo";
|
||||
String logoutEndpoint = "/connect/logout";
|
||||
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
|
||||
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
|
||||
|
||||
@@ -101,6 +102,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
.tokenEndpoint(tokenEndpoint)
|
||||
.jwkSetEndpoint(jwkSetEndpoint)
|
||||
.oidcUserInfoEndpoint(userInfoEndpoint)
|
||||
.oidcLogoutEndpoint(logoutEndpoint)
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint)
|
||||
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint)
|
||||
.build();
|
||||
@@ -132,6 +134,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"userinfo_endpoint\":\"https://example.com/issuer1/userinfo\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"end_session_endpoint\":\"https://example.com/issuer1/connect/logout\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"]");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
* Tests for {@link AuthorizationServerSettings}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class AuthorizationServerSettingsTests {
|
||||
|
||||
@@ -39,6 +40,7 @@ public class AuthorizationServerSettingsTests {
|
||||
assertThat(authorizationServerSettings.getTokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect");
|
||||
assertThat(authorizationServerSettings.getOidcClientRegistrationEndpoint()).isEqualTo("/connect/register");
|
||||
assertThat(authorizationServerSettings.getOidcUserInfoEndpoint()).isEqualTo("/userinfo");
|
||||
assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo("/connect/logout");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,6 +52,7 @@ public class AuthorizationServerSettingsTests {
|
||||
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
|
||||
String oidcClientRegistrationEndpoint = "/connect/v1/register";
|
||||
String oidcUserInfoEndpoint = "/connect/v1/userinfo";
|
||||
String oidcLogoutEndpoint = "/connect/v1/logout";
|
||||
String issuer = "https://example.com:9000";
|
||||
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder()
|
||||
@@ -62,6 +65,7 @@ public class AuthorizationServerSettingsTests {
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint)
|
||||
.oidcClientRegistrationEndpoint(oidcClientRegistrationEndpoint)
|
||||
.oidcUserInfoEndpoint(oidcUserInfoEndpoint)
|
||||
.oidcLogoutEndpoint(oidcLogoutEndpoint)
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerSettings.getIssuer()).isEqualTo(issuer);
|
||||
@@ -72,6 +76,7 @@ public class AuthorizationServerSettingsTests {
|
||||
assertThat(authorizationServerSettings.getTokenIntrospectionEndpoint()).isEqualTo(tokenIntrospectionEndpoint);
|
||||
assertThat(authorizationServerSettings.getOidcClientRegistrationEndpoint()).isEqualTo(oidcClientRegistrationEndpoint);
|
||||
assertThat(authorizationServerSettings.getOidcUserInfoEndpoint()).isEqualTo(oidcUserInfoEndpoint);
|
||||
assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo(oidcLogoutEndpoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,7 +86,7 @@ public class AuthorizationServerSettingsTests {
|
||||
.settings(settings -> settings.put("name2", "value2"))
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerSettings.getSettings()).hasSize(9);
|
||||
assertThat(authorizationServerSettings.getSettings()).hasSize(10);
|
||||
assertThat(authorizationServerSettings.<String>getSetting("name1")).isEqualTo("value1");
|
||||
assertThat(authorizationServerSettings.<String>getSetting("name2")).isEqualTo("value2");
|
||||
}
|
||||
@@ -142,4 +147,11 @@ public class AuthorizationServerSettingsTests {
|
||||
.withMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oidcLogoutEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AuthorizationServerSettings.builder().oidcLogoutEndpoint(null))
|
||||
.withMessage("value cannot be null");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.token;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -27,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
@@ -46,7 +48,6 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
@@ -67,7 +68,7 @@ public class JwtGeneratorTests {
|
||||
private JwtEncoder jwtEncoder;
|
||||
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||
private JwtGenerator jwtGenerator;
|
||||
private AuthorizationServerContext authorizationServerContext;
|
||||
private TestAuthorizationServerContext authorizationServerContext;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
@@ -168,16 +169,21 @@ public class JwtGeneratorTests {
|
||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||
|
||||
Authentication principal = authorization.getAttribute(Principal.class.getName());
|
||||
SessionInformation sessionInformation = new SessionInformation(
|
||||
principal.getPrincipal(), "session1", Date.from(Instant.now().minus(2, ChronoUnit.HOURS)));
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(authorization.getAttribute(Principal.class.getName()))
|
||||
.principal(principal)
|
||||
.authorizationServerContext(this.authorizationServerContext)
|
||||
.authorization(authorization)
|
||||
.authorizedScopes(authorization.getAuthorizedScopes())
|
||||
.tokenType(ID_TOKEN_TOKEN_TYPE)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrant(authentication)
|
||||
.put(SessionInformation.class, sessionInformation)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
@@ -238,6 +244,10 @@ public class JwtGeneratorTests {
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE);
|
||||
assertThat(jwtClaimsSet.<String>getClaim(IdTokenClaimNames.NONCE)).isEqualTo(nonce);
|
||||
|
||||
SessionInformation sessionInformation = tokenContext.get(SessionInformation.class);
|
||||
assertThat(jwtClaimsSet.<String>getClaim("sid")).isEqualTo(sessionInformation.getSessionId());
|
||||
assertThat(jwtClaimsSet.<Date>getClaim(IdTokenClaimNames.AUTH_TIME)).isEqualTo(sessionInformation.getLastRequest());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -151,6 +152,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.hasMessage("authenticationFailureHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSessionAuthenticationStrategyWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setSessionAuthenticationStrategy(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("sessionAuthenticationStrategy cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotAuthorizationRequestThenNotProcessed() throws Exception {
|
||||
String requestUri = "/path";
|
||||
@@ -383,6 +391,31 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), same(authenticationException));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomSessionAuthenticationStrategyThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal, this.authorizationCode,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
|
||||
SessionAuthenticationStrategy sessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class);
|
||||
this.filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verifyNoInteractions(filterChain);
|
||||
verify(sessionAuthenticationStrategy).onAuthentication(same(authorizationCodeRequestAuthenticationResult), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
|
||||
@@ -8,6 +8,7 @@ CREATE TABLE oauth2RegisteredClient (
|
||||
clientAuthenticationMethods varchar(1000) NOT NULL,
|
||||
authorizationGrantTypes varchar(1000) NOT NULL,
|
||||
redirectUris varchar(1000) DEFAULT NULL,
|
||||
postLogoutRedirectUris varchar(1000) DEFAULT NULL,
|
||||
scopes varchar(1000) NOT NULL,
|
||||
clientSettings varchar(2000) NOT NULL,
|
||||
tokenSettings varchar(2000) NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user