Эх сурвалжийг харах

Polish gh-1068

Issue gh-1077
Joe Grandja 2 жил өмнө
parent
commit
5f39c85264
24 өөрчлөгдсөн 285 нэмэгдсэн , 150 устгасан
  1. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
  2. 3 3
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java
  3. 26 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java
  4. 0 28
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java
  5. 4 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java
  6. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java
  7. 3 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java
  8. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java
  9. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimNames.java
  10. 2 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java
  11. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java
  12. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java
  13. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimNames.java
  14. 21 15
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java
  15. 27 27
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java
  16. 14 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java
  17. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java
  18. 2 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java
  19. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java
  20. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java
  21. 94 7
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java
  22. 48 10
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProviderTests.java
  23. 26 26
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationTokenTests.java
  24. 5 13
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilterTests.java

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

@@ -282,7 +282,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
 	 * Sets the {@link SessionRegistry} used to track OpenID Connect sessions.
 	 *
 	 * @param sessionRegistry the {@link SessionRegistry} used to track OpenID Connect sessions
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public void setSessionRegistry(SessionRegistry sessionRegistry) {
 		Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");

+ 3 - 3
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java

@@ -152,7 +152,7 @@ public class RegisteredClient implements Serializable {
 	 * that the End-User's User Agent be redirected to after a logout has been performed.
 	 *
 	 * @return the {@code Set} of post logout redirect URI(s)
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public Set<String> getPostLogoutRedirectUris() {
 		return this.postLogoutRedirectUris;
@@ -447,7 +447,7 @@ public class RegisteredClient implements Serializable {
 		 *
 		 * @param postLogoutRedirectUri the post logout redirect URI
 		 * @return the {@link Builder}
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public Builder postLogoutRedirectUri(String postLogoutRedirectUri) {
 			this.postLogoutRedirectUris.add(postLogoutRedirectUri);
@@ -460,7 +460,7 @@ public class RegisteredClient implements Serializable {
 		 *
 		 * @param postLogoutRedirectUrisConsumer a {@link Consumer} of the post logout redirect URI(s)
 		 * @return the {@link Builder}
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public Builder postLogoutRedirectUris(Consumer<Set<String>> postLogoutRedirectUrisConsumer) {
 			postLogoutRedirectUrisConsumer.accept(this.postLogoutRedirectUris);

+ 26 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java

@@ -23,14 +23,19 @@ import java.util.Map;
 
 import com.nimbusds.jose.jwk.source.JWKSource;
 
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.GenericApplicationListenerAdapter;
+import org.springframework.context.event.SmartApplicationListener;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
+import org.springframework.security.context.DelegatingApplicationListener;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.session.SessionRegistry;
+import org.springframework.security.core.session.SessionRegistryImpl;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2Token;
@@ -270,7 +275,8 @@ public final class OAuth2AuthorizationServerConfigurer
 
 		if (isOidcEnabled()) {
 			// Add OpenID Connect session tracking capabilities.
-			SessionRegistry sessionRegistry = OAuth2ConfigurerUtils.getSessionRegistry(httpSecurity);
+			initSessionRegistry(httpSecurity);
+			SessionRegistry sessionRegistry = httpSecurity.getSharedObject(SessionRegistry.class);
 			OAuth2AuthorizationEndpointConfigurer authorizationEndpointConfigurer =
 					getConfigurer(OAuth2AuthorizationEndpointConfigurer.class);
 			authorizationEndpointConfigurer.setSessionAuthenticationStrategy((authentication, request, response) -> {
@@ -388,4 +394,23 @@ public final class OAuth2AuthorizationServerConfigurer
 		}
 	}
 
+	private static void initSessionRegistry(HttpSecurity httpSecurity) {
+		SessionRegistry sessionRegistry = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, SessionRegistry.class);
+		if (sessionRegistry == null) {
+			sessionRegistry = new SessionRegistryImpl();
+			registerDelegateApplicationListener(httpSecurity, (SessionRegistryImpl) sessionRegistry);
+		}
+		httpSecurity.setSharedObject(SessionRegistry.class, sessionRegistry);
+	}
+
+	private static void registerDelegateApplicationListener(HttpSecurity httpSecurity, ApplicationListener<?> delegate) {
+		DelegatingApplicationListener delegatingApplicationListener =
+				OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, DelegatingApplicationListener.class);
+		if (delegatingApplicationListener == null) {
+			return;
+		}
+		SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate);
+		delegatingApplicationListener.addListener(smartListener);
+	}
+
 }

+ 0 - 28
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java

@@ -24,14 +24,8 @@ import org.springframework.beans.factory.BeanFactoryUtils;
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationListener;
-import org.springframework.context.event.GenericApplicationListenerAdapter;
-import org.springframework.context.event.SmartApplicationListener;
 import org.springframework.core.ResolvableType;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.context.DelegatingApplicationListener;
-import org.springframework.security.core.session.SessionRegistry;
-import org.springframework.security.core.session.SessionRegistryImpl;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
 import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
@@ -186,28 +180,6 @@ final class OAuth2ConfigurerUtils {
 		return authorizationServerSettings;
 	}
 
-	static SessionRegistry getSessionRegistry(HttpSecurity httpSecurity) {
-		SessionRegistry sessionRegistry = httpSecurity.getSharedObject(SessionRegistry.class);
-		if (sessionRegistry == null) {
-			sessionRegistry = getOptionalBean(httpSecurity, SessionRegistry.class);
-			if (sessionRegistry == null) {
-				sessionRegistry = new SessionRegistryImpl();
-				registerDelegateApplicationListener(httpSecurity, (SessionRegistryImpl) sessionRegistry);
-			}
-			httpSecurity.setSharedObject(SessionRegistry.class, sessionRegistry);
-		}
-		return sessionRegistry;
-	}
-
-	private static void registerDelegateApplicationListener(HttpSecurity httpSecurity, ApplicationListener<?> delegate) {
-		DelegatingApplicationListener delegatingApplicationListener = getOptionalBean(httpSecurity, DelegatingApplicationListener.class);
-		if (delegatingApplicationListener == null) {
-			return;
-		}
-		SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate);
-		delegatingApplicationListener.addListener(smartListener);
-	}
-
 	static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {
 		return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type);
 	}

+ 4 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java

@@ -220,11 +220,13 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
 
 		OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity);
 		OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity);
-		SessionRegistry sessionRegistry = OAuth2ConfigurerUtils.getSessionRegistry(httpSecurity);
 
 		OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
 				new OAuth2AuthorizationCodeAuthenticationProvider(authorizationService, tokenGenerator);
-		authorizationCodeAuthenticationProvider.setSessionRegistry(sessionRegistry);
+		SessionRegistry sessionRegistry = httpSecurity.getSharedObject(SessionRegistry.class);
+		if (sessionRegistry != null) {
+			authorizationCodeAuthenticationProvider.setSessionRegistry(sessionRegistry);
+		}
 		authenticationProviders.add(authorizationCodeAuthenticationProvider);
 
 		OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java

@@ -72,7 +72,7 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
 	 *
 	 * @param logoutEndpointCustomizer the {@link Customizer} providing access to the {@link OidcLogoutEndpointConfigurer}
 	 * @return the {@link OidcConfigurer} for further configuration
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public OidcConfigurer logoutEndpoint(Customizer<OidcLogoutEndpointConfigurer> logoutEndpointCustomizer) {
 		logoutEndpointCustomizer.customize(getConfigurer(OidcLogoutEndpointConfigurer.class));

+ 3 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java

@@ -26,6 +26,7 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.session.SessionRegistry;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationProvider;
@@ -47,7 +48,7 @@ import org.springframework.util.Assert;
  * Configurer for OpenID Connect 1.0 RP-Initiated Logout Endpoint.
  *
  * @author Joe Grandja
- * @since 1.1.0
+ * @since 1.1
  * @see OidcConfigurer#logoutEndpoint
  * @see OidcLogoutEndpointFilter
  */
@@ -210,7 +211,7 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer
 				new OidcLogoutAuthenticationProvider(
 						OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
 						OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
-						OAuth2ConfigurerUtils.getSessionRegistry(httpSecurity));
+						httpSecurity.getSharedObject(SessionRegistry.class));
 		authenticationProviders.add(oidcLogoutAuthenticationProvider);
 
 		return authenticationProviders;

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java

@@ -101,7 +101,7 @@ public interface OidcClientMetadataClaimAccessor extends ClaimAccessor {
 	 * that the End-User's User Agent be redirected to after a logout has been performed.
 	 *
 	 * @return the post logout redirection {@code URI} values used by the Client
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	default List<String> getPostLogoutRedirectUris() {
 		return getClaimAsStringList(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS);

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimNames.java

@@ -66,7 +66,7 @@ public final class OidcClientMetadataClaimNames {
 	 * {@code post_logout_redirect_uris} - the post logout redirection {@code URI} values used by the Client.
 	 * The {@code post_logout_redirect_uri} parameter is used by the client when requesting
 	 * that the End-User's User Agent be redirected to after a logout has been performed.
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public static final String POST_LOGOUT_REDIRECT_URIS = "post_logout_redirect_uris";
 

+ 2 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java

@@ -176,7 +176,7 @@ public final class OidcClientRegistration implements OidcClientMetadataClaimAcce
 		 *
 		 * @param postLogoutRedirectUri the post logout redirection {@code URI} used by the Client
 		 * @return the {@link Builder} for further configuration
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public Builder postLogoutRedirectUri(String postLogoutRedirectUri) {
 			addClaimToClaimList(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUri);
@@ -189,7 +189,7 @@ public final class OidcClientRegistration implements OidcClientMetadataClaimAcce
 		 *
 		 * @param postLogoutRedirectUrisConsumer a {@code Consumer} of the post logout redirection {@code URI} values used by the Client
 		 * @return the {@link Builder} for further configuration
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public Builder postLogoutRedirectUris(Consumer<List<String>> postLogoutRedirectUrisConsumer) {
 			acceptClaimValues(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUrisConsumer);

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java

@@ -136,7 +136,7 @@ public final class OidcProviderConfiguration extends AbstractOAuth2Authorization
 		 *
 		 * @param endSessionEndpoint the {@code URL} of the OpenID Connect 1.0 End Session Endpoint
 		 * @return the {@link Builder} for further configuration
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public Builder endSessionEndpoint(String endSessionEndpoint) {
 			return claim(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, endSessionEndpoint);

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java

@@ -72,7 +72,7 @@ public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationSe
 	 * Returns the {@code URL} of the OpenID Connect 1.0 End Session Endpoint {@code (end_session_endpoint)}.
 	 *
 	 * @return the {@code URL} of the OpenID Connect 1.0 End Session Endpoint
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	default URL getEndSessionEndpoint() {
 		return getClaimAsURL(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT);

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimNames.java

@@ -49,7 +49,7 @@ public final class OidcProviderMetadataClaimNames extends OAuth2AuthorizationSer
 
 	/**
 	 * {@code end_session_endpoint} - the {@code URL} of the OpenID Connect 1.0 End Session Endpoint
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public static final String END_SESSION_ENDPOINT = "end_session_endpoint";
 

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

@@ -46,7 +46,7 @@ import org.springframework.util.StringUtils;
  * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 RP-Initiated Logout Endpoint.
  *
  * @author Joe Grandja
- * @since 1.1.0
+ * @since 1.1
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see SessionRegistry
@@ -83,7 +83,7 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro
 				(OidcLogoutAuthenticationToken) authentication;
 
 		OAuth2Authorization authorization = this.authorizationService.findByToken(
-				oidcLogoutAuthentication.getIdToken(), ID_TOKEN_TOKEN_TYPE);
+				oidcLogoutAuthentication.getIdTokenHint(), ID_TOKEN_TOKEN_TYPE);
 		if (authorization == null) {
 			throwError(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint");
 		}
@@ -120,18 +120,24 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro
 			this.logger.trace("Validated logout request parameters");
 		}
 
-		// Validate user session
-		SessionInformation sessionInformation = null;
+		// Validate user identity
 		Authentication userPrincipal = (Authentication) oidcLogoutAuthentication.getPrincipal();
-		if (isPrincipalAuthenticated(userPrincipal) &&
-				StringUtils.hasText(oidcLogoutAuthentication.getSessionId())) {
-			sessionInformation = findSessionInformation(
-					userPrincipal, oidcLogoutAuthentication.getSessionId());
-			if (sessionInformation != null) {
-				String sidClaim = idToken.getClaim("sid");
-				if (!StringUtils.hasText(sidClaim) ||
-						!sidClaim.equals(sessionInformation.getSessionId())) {
-					throwError(OAuth2ErrorCodes.INVALID_TOKEN, "sid");
+		if (isPrincipalAuthenticated(userPrincipal)) {
+			if (!StringUtils.hasText(idToken.getSubject()) ||
+					!idToken.getSubject().equals(userPrincipal.getName())) {
+				throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.SUB);
+			}
+
+			// Check for active session
+			if (StringUtils.hasText(oidcLogoutAuthentication.getSessionId())) {
+				SessionInformation sessionInformation = findSessionInformation(
+						userPrincipal, oidcLogoutAuthentication.getSessionId());
+				if (sessionInformation != null) {
+					String sidClaim = idToken.getClaim("sid");
+					if (!StringUtils.hasText(sidClaim) ||
+							!sidClaim.equals(sessionInformation.getSessionId())) {
+						throwError(OAuth2ErrorCodes.INVALID_TOKEN, "sid");
+					}
 				}
 			}
 		}
@@ -140,8 +146,8 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro
 			this.logger.trace("Authenticated logout request");
 		}
 
-		return new OidcLogoutAuthenticationToken(oidcLogoutAuthentication.getIdToken(), userPrincipal,
-				sessionInformation, oidcLogoutAuthentication.getClientId(),
+		return new OidcLogoutAuthenticationToken(idToken, userPrincipal,
+				oidcLogoutAuthentication.getSessionId(), oidcLogoutAuthentication.getClientId(),
 				oidcLogoutAuthentication.getPostLogoutRedirectUri(), oidcLogoutAuthentication.getState());
 	}
 

+ 27 - 27
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java

@@ -20,7 +20,7 @@ import java.util.Collections;
 import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.core.session.SessionInformation;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
 import org.springframework.util.Assert;
 
@@ -28,16 +28,16 @@ import org.springframework.util.Assert;
  * An {@link Authentication} implementation used for OpenID Connect 1.0 RP-Initiated Logout Endpoint.
  *
  * @author Joe Grandja
- * @since 1.1.0
+ * @since 1.1
  * @see AbstractAuthenticationToken
  * @see OidcLogoutAuthenticationProvider
  */
 public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
 	private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
-	private final String idToken;
+	private final String idTokenHint;
+	private final OidcIdToken idToken;
 	private final Authentication principal;
 	private final String sessionId;
-	private final SessionInformation sessionInformation;
 	private final String clientId;
 	private final String postLogoutRedirectUri;
 	private final String state;
@@ -45,22 +45,22 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
 	/**
 	 * Constructs an {@code OidcLogoutAuthenticationToken} using the provided parameters.
 	 *
-	 * @param idToken the ID Token previously issued by the Provider to the Client and used as a hint about the End-User's current authenticated session with the Client
+	 * @param idTokenHint the ID Token previously issued by the Provider to the Client and used as a hint about the End-User's current authenticated session with the Client
 	 * @param principal the authenticated principal representing the End-User
-	 * @param sessionId the End-User's current authenticated session identifier with the Client
+	 * @param sessionId the End-User's current authenticated session identifier with the Provider
 	 * @param clientId the client identifier the ID Token was issued to
 	 * @param postLogoutRedirectUri the URI which the Client is requesting that the End-User's User Agent be redirected to after a logout has been performed
 	 * @param state the opaque value used by the Client to maintain state between the logout request and the callback to the {@code postLogoutRedirectUri}
 	 */
-	public OidcLogoutAuthenticationToken(String idToken, Authentication principal, @Nullable String sessionId,
+	public OidcLogoutAuthenticationToken(String idTokenHint, Authentication principal, @Nullable String sessionId,
 			@Nullable String clientId, @Nullable String postLogoutRedirectUri, @Nullable String state) {
 		super(Collections.emptyList());
-		Assert.hasText(idToken, "idToken cannot be empty");
+		Assert.hasText(idTokenHint, "idTokenHint cannot be empty");
 		Assert.notNull(principal, "principal cannot be null");
-		this.idToken = idToken;
+		this.idTokenHint = idTokenHint;
+		this.idToken = null;
 		this.principal = principal;
 		this.sessionId = sessionId;
-		this.sessionInformation = null;
 		this.clientId = clientId;
 		this.postLogoutRedirectUri = postLogoutRedirectUri;
 		this.state = state;
@@ -70,22 +70,22 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
 	/**
 	 * Constructs an {@code OidcLogoutAuthenticationToken} using the provided parameters.
 	 *
-	 * @param idToken the ID Token previously issued by the Provider to the Client and used as a hint about the End-User's current authenticated session with the Client
+	 * @param idToken the ID Token previously issued by the Provider to the Client
 	 * @param principal the authenticated principal representing the End-User
-	 * @param sessionInformation  the End-User's current authenticated session information with the Client
+	 * @param sessionId the End-User's current authenticated session identifier with the Provider
 	 * @param clientId the client identifier the ID Token was issued to
 	 * @param postLogoutRedirectUri the URI which the Client is requesting that the End-User's User Agent be redirected to after a logout has been performed
 	 * @param state the opaque value used by the Client to maintain state between the logout request and the callback to the {@code postLogoutRedirectUri}
 	 */
-	public OidcLogoutAuthenticationToken(String idToken, Authentication principal, @Nullable SessionInformation sessionInformation,
+	public OidcLogoutAuthenticationToken(OidcIdToken idToken, Authentication principal, @Nullable String sessionId,
 			@Nullable String clientId, @Nullable String postLogoutRedirectUri, @Nullable String state) {
 		super(Collections.emptyList());
-		Assert.hasText(idToken, "idToken cannot be empty");
+		Assert.notNull(idToken, "idToken cannot be null");
 		Assert.notNull(principal, "principal cannot be null");
+		this.idTokenHint = idToken.getTokenValue();
 		this.idToken = idToken;
 		this.principal = principal;
-		this.sessionId = sessionInformation != null ? sessionInformation.getSessionId() : null;
-		this.sessionInformation = sessionInformation;
+		this.sessionId = sessionId;
 		this.clientId = clientId;
 		this.postLogoutRedirectUri = postLogoutRedirectUri;
 		this.state = state;
@@ -95,7 +95,7 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
 	/**
 	 * Returns the authenticated principal representing the End-User.
 	 *
-	 * @return the authenticated principal
+	 * @return the authenticated principal representing the End-User
 	 */
 	@Override
 	public Object getPrincipal() {
@@ -113,28 +113,28 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
 	 *
 	 * @return the ID Token previously issued by the Provider to the Client
 	 */
-	public String getIdToken() {
-		return this.idToken;
+	public String getIdTokenHint() {
+		return this.idTokenHint;
 	}
 
 	/**
-	 * Returns the End-User's current authenticated session identifier with the Client.
+	 * Returns the ID Token previously issued by the Provider to the Client.
 	 *
-	 * @return the End-User's current authenticated session identifier
+	 * @return the ID Token previously issued by the Provider to the Client
 	 */
 	@Nullable
-	public String getSessionId() {
-		return this.sessionId;
+	public OidcIdToken getIdToken() {
+		return this.idToken;
 	}
 
 	/**
-	 * Returns the End-User's current authenticated session information with the Client.
+	 * Returns the End-User's current authenticated session identifier with the Provider.
 	 *
-	 * @return the End-User's current authenticated session information
+	 * @return the End-User's current authenticated session identifier with the Provider
 	 */
 	@Nullable
-	public SessionInformation getSessionInformation() {
-		return this.sessionInformation;
+	public String getSessionId() {
+		return this.sessionId;
 	}
 
 	/**

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

@@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.core.log.LogMessage;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -58,7 +59,7 @@ import org.springframework.web.util.UriUtils;
  * A {@code Filter} that processes OpenID Connect 1.0 RP-Initiated Logout Requests.
  *
  * @author Joe Grandja
- * @since 1.1.0
+ * @since 1.1
  * @see OidcLogoutAuthenticationConverter
  * @see OidcLogoutAuthenticationProvider
  * @see <a href="https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout">2. RP-Initiated Logout</a>
@@ -182,7 +183,7 @@ public final class OidcLogoutEndpointFilter extends OncePerRequestFilter {
 		OidcLogoutAuthenticationToken oidcLogoutAuthentication = (OidcLogoutAuthenticationToken) authentication;
 
 		// Check for active user session
-		if (oidcLogoutAuthentication.getSessionInformation() != null) {
+		if (isSessionActive(oidcLogoutAuthentication)) {
 			// Perform logout
 			this.logoutHandler.logout(request, response,
 					(Authentication) oidcLogoutAuthentication.getPrincipal());
@@ -215,4 +216,15 @@ public final class OidcLogoutEndpointFilter extends OncePerRequestFilter {
 		response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
 	}
 
+	private static boolean isSessionActive(OidcLogoutAuthenticationToken oidcLogoutAuthentication) {
+		return isPrincipalAuthenticated((Authentication) oidcLogoutAuthentication.getPrincipal()) &&
+				StringUtils.hasText(oidcLogoutAuthentication.getSessionId());
+	}
+
+	private static boolean isPrincipalAuthenticated(Authentication principal) {
+		return principal != null &&
+				!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) &&
+				principal.isAuthenticated();
+	}
+
 }

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

@@ -40,7 +40,7 @@ import org.springframework.util.StringUtils;
  * and then converts to an {@link OidcLogoutAuthenticationToken} used for authenticating the request.
  *
  * @author Joe Grandja
- * @since 1.1.0
+ * @since 1.1
  * @see AuthenticationConverter
  * @see OidcLogoutAuthenticationToken
  * @see OidcLogoutEndpointFilter

+ 2 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java

@@ -130,7 +130,7 @@ public final class AuthorizationServerSettings extends AbstractSettings {
 	 * Returns the OpenID Connect 1.0 Logout endpoint. The default is {@code /connect/logout}.
 	 *
 	 * @return the OpenID Connect 1.0 Logout endpoint
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public String getOidcLogoutEndpoint() {
 		return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT);
@@ -282,7 +282,7 @@ public final class AuthorizationServerSettings extends AbstractSettings {
 		 *
 		 * @param oidcLogoutEndpoint the OpenID Connect 1.0 Logout endpoint
 		 * @return the {@link Builder} for further configuration
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public Builder oidcLogoutEndpoint(String oidcLogoutEndpoint) {
 			return setting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT, oidcLogoutEndpoint);

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java

@@ -128,7 +128,7 @@ public final class ConfigurationSettingNames {
 
 		/**
 		 * Set the OpenID Connect 1.0 Logout endpoint.
-		 * @since 1.1.0
+		 * @since 1.1
 		 */
 		public static final String OIDC_LOGOUT_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("oidc-logout-endpoint");
 

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

@@ -249,7 +249,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
 	 * If OpenID Connect is enabled, the default implementation tracks OpenID Connect sessions using a {@link SessionRegistry}.
 	 *
 	 * @param sessionAuthenticationStrategy the {@link SessionAuthenticationStrategy} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
-	 * @since 1.1.0
+	 * @since 1.1
 	 */
 	public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
 		Assert.notNull(sessionAuthenticationStrategy, "sessionAuthenticationStrategy cannot be null");

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

@@ -54,6 +54,8 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.session.SessionRegistry;
+import org.springframework.security.core.session.SessionRegistryImpl;
 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -131,6 +133,7 @@ public class OidcTests {
 	private static JWKSource<SecurityContext> jwkSource;
 	private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
 			new OAuth2AccessTokenResponseHttpMessageConverter();
+	private static SessionRegistry sessionRegistry;
 
 	public final SpringTestContext spring = new SpringTestContext();
 
@@ -163,6 +166,7 @@ public class OidcTests {
 				.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
 				.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
 				.build();
+		sessionRegistry = spy(new SessionRegistryImpl());
 	}
 
 	@AfterEach
@@ -187,8 +191,8 @@ public class OidcTests {
 
 		MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient);
 		MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
-				.params(authorizationRequestParameters)
-				.with(user("user").roles("A", "B")))
+						.params(authorizationRequestParameters)
+						.with(user("user").roles("A", "B")))
 				.andExpect(status().is3xxRedirection())
 				.andReturn();
 		String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -199,9 +203,9 @@ public class OidcTests {
 		OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
 
 		mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
-				.params(getTokenRequestParameters(registeredClient, authorization))
-				.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
-						registeredClient.getClientId(), registeredClient.getClientSecret())))
+						.params(getTokenRequestParameters(registeredClient, authorization))
+						.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
+								registeredClient.getClientId(), registeredClient.getClientSecret())))
 				.andExpect(status().isOk())
 				.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
 				.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
@@ -259,8 +263,7 @@ public class OidcTests {
 		mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
 						.params(getTokenRequestParameters(registeredClient, authorization))
 						.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
-								registeredClient.getClientId(), registeredClient.getClientSecret()))
-						.session(session))
+								registeredClient.getClientId(), registeredClient.getClientSecret())))
 				.andExpect(status().isOk())
 				.andReturn();
 
@@ -283,6 +286,85 @@ public class OidcTests {
 		assertThat(session.isInvalid()).isTrue();
 	}
 
+	@Test
+	public void requestWhenLogoutRequestWithOtherUsersIdTokenThenNotLogout() throws Exception {
+		this.spring.register(AuthorizationServerConfiguration.class).autowire();
+
+		// Login user1
+		RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
+		this.registeredClientRepository.save(registeredClient1);
+
+		MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient1);
+		MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
+						.params(authorizationRequestParameters)
+						.with(user("user1")))
+				.andExpect(status().is3xxRedirection())
+				.andReturn();
+
+		MockHttpSession user1Session = (MockHttpSession) mvcResult.getRequest().getSession();
+		assertThat(user1Session.isNew()).isTrue();
+
+		String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
+		String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code");
+		OAuth2Authorization user1Authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
+
+		mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
+						.params(getTokenRequestParameters(registeredClient1, user1Authorization))
+						.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
+								registeredClient1.getClientId(), registeredClient1.getClientSecret())))
+				.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 user1IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
+
+		// Login user2
+		RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().scope(OidcScopes.OPENID).build();
+		this.registeredClientRepository.save(registeredClient2);
+
+		authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2);
+		mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
+						.params(authorizationRequestParameters)
+						.with(user("user2")))
+				.andExpect(status().is3xxRedirection())
+				.andReturn();
+
+		MockHttpSession user2Session = (MockHttpSession) mvcResult.getRequest().getSession();
+		assertThat(user2Session.isNew()).isTrue();
+
+		redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
+		authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code");
+		OAuth2Authorization user2Authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
+
+		mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
+						.params(getTokenRequestParameters(registeredClient2, user2Authorization))
+						.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
+								registeredClient2.getClientId(), registeredClient2.getClientSecret())))
+				.andExpect(status().isOk())
+				.andReturn();
+
+		servletResponse = mvcResult.getResponse();
+		httpResponse = new MockClientHttpResponse(
+				servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
+		accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
+
+		String user2IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
+
+		// Attempt to log out user1 using user2's ID Token
+		mvcResult = this.mvc.perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)
+						.param("id_token_hint", user2IdToken)
+						.session(user1Session))
+				.andExpect(status().isBadRequest())
+				.andExpect(status().reason("[invalid_token] OpenID Connect 1.0 Logout Request Parameter: sub"))
+				.andReturn();
+
+		assertThat(user1Session.isInvalid()).isFalse();
+	}
+
 	@Test
 	public void requestWhenCustomTokenGeneratorThenUsed() throws Exception {
 		this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire();
@@ -403,6 +485,11 @@ public class OidcTests {
 			return NoOpPasswordEncoder.getInstance();
 		}
 
+		@Bean
+		SessionRegistry sessionRegistry() {
+			return sessionRegistry;
+		}
+
 		static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
 
 			RowMapper(RegisteredClientRepository registeredClientRepository) {

+ 48 - 10
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProviderTests.java

@@ -126,7 +126,7 @@ public class OidcLogoutAuthenticationProviderTests {
 				});
 
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 	}
 
 	@Test
@@ -160,7 +160,7 @@ public class OidcLogoutAuthenticationProviderTests {
 					assertThat(error.getDescription()).contains(IdTokenClaimNames.AUD);
 				});
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 		verify(this.registeredClientRepository).findById(
 				eq(authorization.getRegisteredClientId()));
 	}
@@ -197,7 +197,7 @@ public class OidcLogoutAuthenticationProviderTests {
 					assertThat(error.getDescription()).contains(IdTokenClaimNames.AUD);
 				});
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 		verify(this.registeredClientRepository).findById(
 				eq(authorization.getRegisteredClientId()));
 	}
@@ -234,7 +234,7 @@ public class OidcLogoutAuthenticationProviderTests {
 					assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID);
 				});
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 		verify(this.registeredClientRepository).findById(
 				eq(authorization.getRegisteredClientId()));
 	}
@@ -272,7 +272,46 @@ public class OidcLogoutAuthenticationProviderTests {
 					assertThat(error.getDescription()).contains("post_logout_redirect_uri");
 				});
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
+		verify(this.registeredClientRepository).findById(
+				eq(authorization.getRegisteredClientId()));
+	}
+
+	@Test
+	public void authenticateWhenInvalidSubThenThrowOAuth2AuthenticationException() {
+		TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		OidcIdToken idToken =  OidcIdToken.withTokenValue("id-token")
+				.issuer("https://provider.com")
+				.subject("other-sub")
+				.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);
+
+		principal.setAuthenticated(true);
+
+		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("sub");
+				});
+		verify(this.authorizationService).findByToken(
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 		verify(this.registeredClientRepository).findById(
 				eq(authorization.getRegisteredClientId()));
 	}
@@ -317,7 +356,7 @@ public class OidcLogoutAuthenticationProviderTests {
 					assertThat(error.getDescription()).contains("sid");
 				});
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 		verify(this.registeredClientRepository).findById(
 				eq(authorization.getRegisteredClientId()));
 	}
@@ -363,7 +402,7 @@ public class OidcLogoutAuthenticationProviderTests {
 					assertThat(error.getDescription()).contains("sid");
 				});
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE));
 		verify(this.registeredClientRepository).findById(
 				eq(authorization.getRegisteredClientId()));
 	}
@@ -408,15 +447,14 @@ public class OidcLogoutAuthenticationProviderTests {
 				(OidcLogoutAuthenticationToken) this.authenticationProvider.authenticate(authentication);
 
 		verify(this.authorizationService).findByToken(
-				eq(authentication.getIdToken()), eq(ID_TOKEN_TOKEN_TYPE));
+				eq(authentication.getIdTokenHint()), 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.getIdToken()).isEqualTo(idToken);
 		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);

+ 26 - 26
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationTokenTests.java

@@ -15,13 +15,12 @@
  */
 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 org.springframework.security.oauth2.core.oidc.OidcIdToken;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -32,59 +31,60 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
  * @author Joe Grandja
  */
 public class OidcLogoutAuthenticationTokenTests {
-	private final String idToken = "id-token";
+	private final String idTokenHint = "id-token";
+	private final OidcIdToken idToken = OidcIdToken.withTokenValue(this.idTokenHint)
+			.issuer("https://provider.com")
+			.subject("principal")
+			.issuedAt(Instant.now().minusSeconds(60))
+			.expiresAt(Instant.now().plusSeconds(60))
+			.build();
 	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() {
+	public void constructorWhenIdTokenHintEmptyThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
 				.isThrownBy(() -> new OidcLogoutAuthenticationToken(
-						null, this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
-				.withMessage("idToken cannot be empty");
+						"", this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
+				.withMessage("idTokenHint cannot be empty");
 		assertThatIllegalArgumentException()
 				.isThrownBy(() -> new OidcLogoutAuthenticationToken(
-						null, this.principal, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state))
-				.withMessage("idToken cannot be empty");
+						(String) null, this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
+				.withMessage("idTokenHint 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");
+	public void constructorWhenIdTokenNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
 				.isThrownBy(() -> new OidcLogoutAuthenticationToken(
-						"", this.principal, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state))
-				.withMessage("idToken cannot be empty");
+						(OidcIdToken) null, this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
+				.withMessage("idToken cannot be null");
 	}
 
 	@Test
 	public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
 				.isThrownBy(() -> new OidcLogoutAuthenticationToken(
-						this.idToken, null, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
+						this.idTokenHint, 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))
+						this.idToken, null, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
 				.withMessage("principal cannot be null");
 	}
 
 	@Test
-	public void constructorWhenSessionIdProvidedThenCreated() {
+	public void constructorWhenIdTokenHintProvidedThenCreated() {
 		OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
-				this.idToken, this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state);
+				this.idTokenHint, 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.getIdTokenHint()).isEqualTo(this.idTokenHint);
+		assertThat(authentication.getIdToken()).isNull();
 		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);
@@ -92,14 +92,14 @@ public class OidcLogoutAuthenticationTokenTests {
 	}
 
 	@Test
-	public void constructorWhenSessionInformationProvidedThenCreated() {
+	public void constructorWhenIdTokenProvidedThenCreated() {
 		OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
-				this.idToken, this.principal, this.sessionInformation, this.clientId, this.postLogoutRedirectUri, this.state);
+				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.getIdTokenHint()).isEqualTo(this.idToken.getTokenValue());
 		assertThat(authentication.getIdToken()).isEqualTo(this.idToken);
-		assertThat(authentication.getSessionId()).isEqualTo(this.sessionInformation.getSessionId());
-		assertThat(authentication.getSessionInformation()).isEqualTo(this.sessionInformation);
+		assertThat(authentication.getSessionId()).isEqualTo(this.sessionId);
 		assertThat(authentication.getClientId()).isEqualTo(this.clientId);
 		assertThat(authentication.getPostLogoutRedirectUri()).isEqualTo(this.postLogoutRedirectUri);
 		assertThat(authentication.getState()).isEqualTo(this.state);

+ 5 - 13
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilterTests.java

@@ -15,8 +15,6 @@
  */
 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;
@@ -38,7 +36,6 @@ 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;
@@ -217,7 +214,7 @@ public class OidcLogoutEndpointFilterTests {
 	@Test
 	public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
 		OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
-				"id-token", this.principal, (SessionInformation) null, null, null, null);
+				"id-token", this.principal, null, null, null, null);
 
 		AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
 		when(authenticationConverter.convert(any())).thenReturn(authentication);
@@ -240,7 +237,7 @@ public class OidcLogoutEndpointFilterTests {
 	@Test
 	public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception {
 		OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(
-				"id-token", this.principal, (SessionInformation) null, null, null, null);
+				"id-token", this.principal, null, null, null, null);
 
 		AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
 		this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
@@ -292,11 +289,8 @@ public class OidcLogoutEndpointFilterTests {
 		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);
+				"id-token", this.principal, session.getId(), null, null, null);
 
 		when(this.authenticationManager.authenticate(any()))
 				.thenReturn(authentication);
@@ -321,14 +315,12 @@ public class OidcLogoutEndpointFilterTests {
 		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,
+				"id-token", this.principal, session.getId(),
 				registeredClient.getClientId(), postLogoutRedirectUri, state);
+		authentication.setAuthenticated(true);
 
 		when(this.authenticationManager.authenticate(any()))
 				.thenReturn(authentication);