diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index 935599dcc7..2f0ca5c0fd 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -20,10 +20,10 @@ import org.springframework.core.ResolvableType; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.authentication.NimbusAuthorizationCodeTokenExchanger; +import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry; import org.springframework.security.oauth2.client.authentication.jwt.NimbusJwtDecoderRegistry; import org.springframework.security.oauth2.client.authentication.userinfo.CustomUserTypesOAuth2UserService; @@ -33,7 +33,7 @@ import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2 import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.token.SecurityTokenRepository; +import org.springframework.security.oauth2.client.token.OAuth2TokenRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRedirectFilter; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; @@ -132,7 +132,7 @@ public final class OAuth2LoginConfigurer> exten public class TokenEndpointConfig { private AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; - private SecurityTokenRepository accessTokenRepository; + private OAuth2TokenRepository accessTokenRepository; private JwtDecoderRegistry jwtDecoderRegistry; private TokenEndpointConfig() { @@ -146,7 +146,7 @@ public final class OAuth2LoginConfigurer> exten return this; } - public TokenEndpointConfig accessTokenRepository(SecurityTokenRepository accessTokenRepository) { + public TokenEndpointConfig accessTokenRepository(OAuth2TokenRepository accessTokenRepository) { Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null"); this.accessTokenRepository = accessTokenRepository; return this; @@ -249,10 +249,6 @@ public final class OAuth2LoginConfigurer> exten OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(authorizationCodeTokenExchanger, oauth2UserService); - if (this.tokenEndpointConfig.accessTokenRepository != null) { - oauth2LoginAuthenticationProvider.setAccessTokenRepository( - this.tokenEndpointConfig.accessTokenRepository); - } if (this.userInfoEndpointConfig.userAuthoritiesMapper != null) { oauth2LoginAuthenticationProvider.setAuthoritiesMapper( this.userInfoEndpointConfig.userAuthoritiesMapper); @@ -267,10 +263,6 @@ public final class OAuth2LoginConfigurer> exten OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider = new OidcAuthorizationCodeAuthenticationProvider( authorizationCodeTokenExchanger, oidcUserService, jwtDecoderRegistry); - if (this.tokenEndpointConfig.accessTokenRepository != null) { - oidcAuthorizationCodeAuthenticationProvider.setAccessTokenRepository( - this.tokenEndpointConfig.accessTokenRepository); - } if (this.userInfoEndpointConfig.userAuthoritiesMapper != null) { oidcAuthorizationCodeAuthenticationProvider.setAuthoritiesMapper( this.userInfoEndpointConfig.userAuthoritiesMapper); @@ -308,6 +300,10 @@ public final class OAuth2LoginConfigurer> exten authorizationResponseFilter.setAuthorizationRequestRepository( this.authorizationEndpointConfig.authorizationRequestRepository); } + if (this.tokenEndpointConfig.accessTokenRepository != null) { + authorizationResponseFilter.setAccessTokenRepository( + this.tokenEndpointConfig.accessTokenRepository); + } super.configure(http); } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java index 958d574c27..00882c5f50 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java @@ -22,8 +22,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository; -import org.springframework.security.oauth2.client.token.SecurityTokenRepository; import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; @@ -49,7 +47,6 @@ import java.util.Collection; * @author Joe Grandja * @since 5.0 * @see AuthorizationCodeAuthenticationToken - * @see SecurityTokenRepository * @see OAuth2AuthenticationToken * @see AuthorizedClient * @see OAuth2UserService @@ -63,7 +60,6 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; private final AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; private final OAuth2UserService userService; - private SecurityTokenRepository accessTokenRepository = new InMemoryAccessTokenRepository(); private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities); public OAuth2LoginAuthenticationProvider( @@ -121,10 +117,6 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider AuthorizedClient authorizedClient = new AuthorizedClient( authorizationCodeAuthentication.getClientRegistration(), "unknown", accessToken); - this.accessTokenRepository.saveSecurityToken( - authorizedClient.getAccessToken(), - authorizedClient.getClientRegistration()); - OAuth2User oauth2User = this.userService.loadUser(authorizedClient); // Update AuthorizedClient now that we know the 'principalName' @@ -141,11 +133,6 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider return authenticationResult; } - public final void setAccessTokenRepository(SecurityTokenRepository accessTokenRepository) { - Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null"); - this.accessTokenRepository = accessTokenRepository; - } - public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null"); this.authoritiesMapper = authoritiesMapper; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/InMemoryAccessTokenRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/InMemoryAccessTokenRepository.java index fcf91c24ea..5429449a8a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/InMemoryAccessTokenRepository.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/InMemoryAccessTokenRepository.java @@ -15,6 +15,7 @@ */ package org.springframework.security.oauth2.client.token; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AccessToken; import org.springframework.util.Assert; @@ -24,55 +25,42 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** - * A {@link SecurityTokenRepository} that associates an {@link AccessToken} - * to a {@link ClientRegistration Client} and stores it in-memory. + * An in-memory {@link OAuth2TokenRepository} for {@link AccessToken}'s. * * @author Joe Grandja * @since 5.0 - * @see SecurityTokenRepository + * @see OAuth2TokenRepository * @see AccessToken * @see ClientRegistration + * @see Authentication */ -public final class InMemoryAccessTokenRepository implements SecurityTokenRepository { +public final class InMemoryAccessTokenRepository implements OAuth2TokenRepository { private final Map accessTokens = new ConcurrentHashMap<>(); @Override - public AccessToken loadSecurityToken(ClientRegistration registration) { + public AccessToken loadToken(ClientRegistration registration, Authentication principal) { Assert.notNull(registration, "registration cannot be null"); - return this.accessTokens.get(this.getClientIdentifier(registration)); + Assert.notNull(principal, "principal cannot be null"); + return this.accessTokens.get(this.getIdentifier(registration, principal)); } @Override - public void saveSecurityToken(AccessToken accessToken, ClientRegistration registration) { + public void saveToken(AccessToken accessToken, ClientRegistration registration, Authentication principal) { Assert.notNull(accessToken, "accessToken cannot be null"); Assert.notNull(registration, "registration cannot be null"); - this.accessTokens.put(this.getClientIdentifier(registration), accessToken); + Assert.notNull(principal, "principal cannot be null"); + this.accessTokens.put(this.getIdentifier(registration, principal), accessToken); } @Override - public void removeSecurityToken(ClientRegistration registration) { + public AccessToken removeToken(ClientRegistration registration, Authentication principal) { Assert.notNull(registration, "registration cannot be null"); - this.accessTokens.remove(this.getClientIdentifier(registration)); + Assert.notNull(principal, "principal cannot be null"); + return this.accessTokens.remove(this.getIdentifier(registration, principal)); } - /** - * A client is considered "authorized", if it receives a successful response from the Token Endpoint. - * - * @see Section 4.1.3 Access Token Request - * @see Section 5.1 Access Token Response - */ - private String getClientIdentifier(ClientRegistration clientRegistration) { - StringBuilder builder = new StringBuilder(); - - // Access Token Request attributes - builder.append("[").append(clientRegistration.getAuthorizationGrantType().getValue()).append("]"); - builder.append("[").append(clientRegistration.getRedirectUri()).append("]"); - builder.append("[").append(clientRegistration.getClientId()).append("]"); - - // Access Token Response attributes - builder.append("[").append(clientRegistration.getScopes().toString()).append("]"); - - return Base64.getEncoder().encodeToString(builder.toString().getBytes()); + private String getIdentifier(ClientRegistration registration, Authentication principal) { + String identifier = "[" + registration.getRegistrationId() + "][" + principal.getName() + "]"; + return Base64.getEncoder().encodeToString(identifier.getBytes()); } } - diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/SecurityTokenRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/OAuth2TokenRepository.java similarity index 62% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/SecurityTokenRepository.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/OAuth2TokenRepository.java index 1d2d1146e5..874d0dbadd 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/SecurityTokenRepository.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/token/OAuth2TokenRepository.java @@ -15,24 +15,28 @@ */ package org.springframework.security.oauth2.client.token; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AbstractOAuth2Token; /** * Implementations of this interface are responsible for the persistence - * and association of an {@link AbstractOAuth2Token} to a {@link ClientRegistration Client}. + * and association of an {@link AbstractOAuth2Token OAuth 2.0 Token} + * to a {@link ClientRegistration Client} and Resource Owner, + * which is the {@link Authentication Principal} who originally granted the authorization. * * @author Joe Grandja * @since 5.0 * @see AbstractOAuth2Token * @see ClientRegistration + * @see Authentication */ -public interface SecurityTokenRepository { +public interface OAuth2TokenRepository { - T loadSecurityToken(ClientRegistration registration); + T loadToken(ClientRegistration registration, Authentication principal); - void saveSecurityToken(T securityToken, ClientRegistration registration); + void saveToken(T token, ClientRegistration registration, Authentication principal); - void removeSecurityToken(ClientRegistration registration); + T removeToken(ClientRegistration registration, Authentication principal); } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java index f4a8d927c1..8adf3b54a3 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java @@ -18,11 +18,15 @@ package org.springframework.security.oauth2.client.web; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; +import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository; +import org.springframework.security.oauth2.client.token.OAuth2TokenRepository; +import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCode; import org.springframework.security.oauth2.core.endpoint.AuthorizationExchange; @@ -63,12 +67,14 @@ import java.io.IOException; * @since 5.0 * @see AbstractAuthenticationProcessingFilter * @see AuthorizationCodeAuthenticationToken + * @see OAuth2AuthenticationToken * @see OAuth2LoginAuthenticationProvider - * @see AuthorizationResponse * @see AuthorizationRequest + * @see AuthorizationResponse * @see AuthorizationRequestRepository * @see AuthorizationRequestRedirectFilter * @see ClientRegistrationRepository + * @see OAuth2TokenRepository * @see Section 4.1 Authorization Code Grant * @see Section 4.1.2 Authorization Response */ @@ -77,6 +83,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce private static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found"; private ClientRegistrationRepository clientRegistrationRepository; private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); + private OAuth2TokenRepository accessTokenRepository = new InMemoryAccessTokenRepository(); public OAuth2LoginAuthenticationFilter() { this(DEFAULT_FILTER_PROCESSES_URI); @@ -127,7 +134,15 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce clientRegistration, new AuthorizationExchange(authorizationRequest, authorizationResponse)); authorizationCodeAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request)); - return this.getAuthenticationManager().authenticate(authorizationCodeAuthentication); + OAuth2AuthenticationToken oauth2Authentication = + (OAuth2AuthenticationToken) this.getAuthenticationManager().authenticate(authorizationCodeAuthentication); + + this.accessTokenRepository.saveToken( + oauth2Authentication.getAuthorizedClient().getAccessToken(), + oauth2Authentication.getAuthorizedClient().getClientRegistration(), + oauth2Authentication); + + return oauth2Authentication; } public final void setClientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) { @@ -140,6 +155,11 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce this.authorizationRequestRepository = authorizationRequestRepository; } + public final void setAccessTokenRepository(OAuth2TokenRepository accessTokenRepository) { + Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null"); + this.accessTokenRepository = accessTokenRepository; + } + private AuthorizationResponse convert(HttpServletRequest request) { String code = request.getParameter(OAuth2Parameter.CODE); String errorCode = request.getParameter(OAuth2Parameter.ERROR); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java index 8894959f36..e1e7f63732 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java @@ -27,8 +27,6 @@ import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderR import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository; -import org.springframework.security.oauth2.client.token.SecurityTokenRepository; import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; @@ -61,7 +59,7 @@ import java.util.Collection; * @author Joe Grandja * @since 5.0 * @see AuthorizationCodeAuthenticationToken - * @see SecurityTokenRepository + * @see OAuth2AuthenticationToken * @see OidcAuthorizedClient * @see OidcUserService * @see OidcUser @@ -75,7 +73,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati private final AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; private final OAuth2UserService userService; private final JwtDecoderRegistry jwtDecoderRegistry; - private SecurityTokenRepository accessTokenRepository = new InMemoryAccessTokenRepository(); private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities); public OidcAuthorizationCodeAuthenticationProvider( @@ -151,10 +148,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati OidcAuthorizedClient authorizedClient = new OidcAuthorizedClient( clientRegistration, idToken.getSubject(), accessToken, idToken); - this.accessTokenRepository.saveSecurityToken( - authorizedClient.getAccessToken(), - authorizedClient.getClientRegistration()); - OAuth2User oauth2User = this.userService.loadUser(authorizedClient); // Update AuthorizedClient as the 'principalName' may have changed @@ -172,11 +165,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati return authenticationResult; } - public final void setAccessTokenRepository(SecurityTokenRepository accessTokenRepository) { - Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null"); - this.accessTokenRepository = accessTokenRepository; - } - public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null"); this.authoritiesMapper = authoritiesMapper;