Polish gh-1068
Issue gh-1077
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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,30 +113,30 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
|
||||
*
|
||||
* @return the ID Token previously issued by the Provider to the Client
|
||||
*/
|
||||
public String getIdToken() {
|
||||
public String getIdTokenHint() {
|
||||
return this.idTokenHint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID Token previously issued by the Provider to the Client.
|
||||
*
|
||||
* @return the ID Token previously issued by the Provider to the Client
|
||||
*/
|
||||
@Nullable
|
||||
public OidcIdToken getIdToken() {
|
||||
return this.idToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the End-User's current authenticated session identifier with the Client.
|
||||
* Returns the End-User's current authenticated session identifier with the Provider.
|
||||
*
|
||||
* @return the End-User's current authenticated session identifier
|
||||
* @return the End-User's current authenticated session identifier with the Provider
|
||||
*/
|
||||
@Nullable
|
||||
public String getSessionId() {
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the End-User's current authenticated session information with the Client.
|
||||
*
|
||||
* @return the End-User's current authenticated session information
|
||||
*/
|
||||
@Nullable
|
||||
public SessionInformation getSessionInformation() {
|
||||
return this.sessionInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client identifier the ID Token was issued to.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
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() {
|
||||
public void constructorWhenIdTokenHintEmptyThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
"", this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state))
|
||||
.withMessage("idToken cannot be empty");
|
||||
.withMessage("idTokenHint cannot be empty");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
"", 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 constructorWhenIdTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OidcLogoutAuthenticationToken(
|
||||
(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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user