diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java index c45e9c65dd..0a42576198 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java @@ -21,13 +21,10 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticator; -import org.springframework.security.oauth2.client.authentication.AuthorizationGrantAuthenticator; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; -import org.springframework.security.oauth2.client.authentication.DelegatingAuthorizationGrantAuthenticator; +import org.springframework.security.oauth2.client.authentication.NimbusAuthorizationCodeTokenExchanger; 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.NimbusAuthorizationCodeTokenExchanger; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.token.SecurityTokenRepository; import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationFilter; @@ -35,13 +32,10 @@ import org.springframework.security.oauth2.client.web.AuthorizationRequestRedire import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestUriBuilder; import org.springframework.security.oauth2.core.AccessToken; -import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizationCodeAuthenticator; +import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizationCodeAuthenticationProvider; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.util.Assert; -import java.util.ArrayList; -import java.util.List; - /** * A security configurer for the Authorization Code Grant type. * @@ -60,7 +54,6 @@ public class AuthorizationCodeGrantConfigurer> // ***** Authorization Response members private AuthorizationCodeAuthenticationFilter authorizationResponseFilter; private String authorizationResponseBaseUri; - private AuthorizationGrantAuthenticator authorizationCodeAuthenticator; private AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; private SecurityTokenRepository accessTokenRepository; private JwtDecoderRegistry jwtDecoderRegistry; @@ -89,14 +82,6 @@ public class AuthorizationCodeGrantConfigurer> return this; } - public AuthorizationCodeGrantConfigurer authorizationCodeAuthenticator( - AuthorizationGrantAuthenticator authorizationCodeAuthenticator) { - - Assert.notNull(authorizationCodeAuthenticator, "authorizationCodeAuthenticator cannot be null"); - this.authorizationCodeAuthenticator = authorizationCodeAuthenticator; - return this; - } - public AuthorizationCodeGrantConfigurer authorizationCodeTokenExchanger( AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger) { @@ -125,12 +110,20 @@ public class AuthorizationCodeGrantConfigurer> @Override public final void init(B http) throws Exception { - AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider = - new AuthorizationCodeAuthenticationProvider(this.getAuthorizationCodeAuthenticator()); + AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider = + new AuthorizationCodeAuthenticationProvider(this.getAuthorizationCodeTokenExchanger()); if (this.accessTokenRepository != null) { - authorizationCodeAuthenticationProvider.setAccessTokenRepository(this.accessTokenRepository); + oauth2AuthorizationCodeAuthenticationProvider.setAccessTokenRepository(this.accessTokenRepository); } - http.authenticationProvider(this.postProcess(authorizationCodeAuthenticationProvider)); + http.authenticationProvider(this.postProcess(oauth2AuthorizationCodeAuthenticationProvider)); + + OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider = + new OidcAuthorizationCodeAuthenticationProvider( + this.getAuthorizationCodeTokenExchanger(), this.getJwtDecoderRegistry()); + if (this.accessTokenRepository != null) { + oidcAuthorizationCodeAuthenticationProvider.setAccessTokenRepository(this.accessTokenRepository); + } + http.authenticationProvider(this.postProcess(oidcAuthorizationCodeAuthenticationProvider)); this.authorizationRequestFilter = new AuthorizationRequestRedirectFilter( this.getAuthorizationRequestBaseUri(), this.getClientRegistrationRepository()); @@ -180,17 +173,6 @@ public class AuthorizationCodeGrantConfigurer> return this.authorizationRequestRepository; } - private AuthorizationGrantAuthenticator getAuthorizationCodeAuthenticator() { - if (this.authorizationCodeAuthenticator == null) { - List> authenticators = new ArrayList<>(); - authenticators.add(new AuthorizationCodeAuthenticator(this.getAuthorizationCodeTokenExchanger())); - authenticators.add(new OidcAuthorizationCodeAuthenticator( - this.getAuthorizationCodeTokenExchanger(), this.getJwtDecoderRegistry())); - this.authorizationCodeAuthenticator = new DelegatingAuthorizationGrantAuthenticator<>(authenticators);; - } - return this.authorizationCodeAuthenticator; - } - private AuthorizationGrantTokenExchanger getAuthorizationCodeTokenExchanger() { if (this.authorizationCodeTokenExchanger == null) { this.authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger(); 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 64c53933c5..1bc63dee9c 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 @@ -21,12 +21,11 @@ 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.AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.authentication.AuthorizationGrantAuthenticator; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; -import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.userinfo.CustomUserTypesOAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.DelegatingOAuth2UserService; +import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -126,14 +125,6 @@ public final class OAuth2LoginConfigurer> exten private TokenEndpointConfig() { } - public TokenEndpointConfig authorizationCodeAuthenticator( - AuthorizationGrantAuthenticator authorizationCodeAuthenticator) { - - Assert.notNull(authorizationCodeAuthenticator, "authorizationCodeAuthenticator cannot be null"); - authorizationCodeGrantConfigurer.authorizationCodeAuthenticator(authorizationCodeAuthenticator); - return this; - } - public TokenEndpointConfig authorizationCodeTokenExchanger( AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticationProvider.java index ac2521beb6..18a7ed3f9a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticationProvider.java @@ -24,44 +24,37 @@ import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.AuthorizationResponse; -import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken; +import org.springframework.security.oauth2.core.endpoint.TokenResponse; import org.springframework.util.Assert; /** - * An implementation of an {@link AuthenticationProvider} that is responsible for authenticating - * an authorization code credential with the authorization server's Token Endpoint - * and if valid, exchanging it for an access token credential and optionally an - * id token credential (for OpenID Connect Authorization Code Flow). + * An implementation of an {@link AuthenticationProvider} + * for the OAuth 2.0 Authorization Code Grant Flow. * - *

- * The {@link AuthorizationCodeAuthenticationProvider} uses an {@link AuthorizationGrantAuthenticator} - * to authenticate the authorization code credential and ultimately - * return an "Authorized Client" as an {@link OAuth2ClientAuthenticationToken}. + * This {@link AuthenticationProvider} is responsible for authenticating + * an authorization code credential with the authorization server's Token Endpoint + * and if valid, exchanging it for an access token credential. * * @author Joe Grandja * @since 5.0 * @see AuthorizationCodeAuthenticationToken * @see OAuth2ClientAuthenticationToken - * @see OidcClientAuthenticationToken - * @see AuthorizationGrantAuthenticator * @see SecurityTokenRepository * @see Section 4.1 Authorization Code Grant Flow - * @see Section 3.1 OpenID Connect Authorization Code Flow * @see Section 4.1.3 Access Token Request * @see Section 4.1.4 Access Token Response - * @see Section 3.1.3.3 OpenID Connect Token Response */ public class AuthorizationCodeAuthenticationProvider implements AuthenticationProvider { private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; - private final AuthorizationGrantAuthenticator authorizationCodeAuthenticator; + private final AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; private SecurityTokenRepository accessTokenRepository = new InMemoryAccessTokenRepository(); public AuthorizationCodeAuthenticationProvider( - AuthorizationGrantAuthenticator authorizationCodeAuthenticator) { + AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger) { - Assert.notNull(authorizationCodeAuthenticator, "authorizationCodeAuthenticator cannot be null"); - this.authorizationCodeAuthenticator = authorizationCodeAuthenticator; + Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); + this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger; } @Override @@ -69,6 +62,16 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (AuthorizationCodeAuthenticationToken) authentication; + // Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + // scope + // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. + // If the openid scope value is not present, the behavior is entirely unspecified. + if (authorizationCodeAuthentication.getAuthorizationRequest().getScope().contains("openid")) { + // The OpenID Connect implementation of Authorization Code AuthenticationProvider + // should handle OpenID Connect Authentication Requests so don't handle and return null + return null; + } + AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationRequest(); AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationResponse(); @@ -87,8 +90,16 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } + TokenResponse tokenResponse = + this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication); + + AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(), + tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(), + tokenResponse.getExpiresAt(), tokenResponse.getScope()); + OAuth2ClientAuthenticationToken clientAuthentication = - this.authorizationCodeAuthenticator.authenticate(authorizationCodeAuthentication); + new OAuth2ClientAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), accessToken); + clientAuthentication.setDetails(authorizationCodeAuthentication.getDetails()); this.accessTokenRepository.saveSecurityToken( clientAuthentication.getAccessToken(), diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticator.java deleted file mode 100644 index 56604d6913..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.client.authentication; - -import org.springframework.security.oauth2.core.AccessToken; -import org.springframework.security.oauth2.core.endpoint.TokenResponse; -import org.springframework.util.Assert; - -/** - * An implementation of an {@link AuthorizationGrantAuthenticator} that - * "authenticates" an authorization code grant credential - * against an OAuth 2.0 Provider's Token Endpoint. - * - * @author Joe Grandja - * @since 5.0 - * @see AuthorizationCodeAuthenticationToken - * @see AuthorizationGrantTokenExchanger - */ -public class AuthorizationCodeAuthenticator implements AuthorizationGrantAuthenticator { - private final AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; - - public AuthorizationCodeAuthenticator(AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger) { - Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); - this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger; - } - - @Override - public OAuth2ClientAuthenticationToken authenticate( - AuthorizationCodeAuthenticationToken authorizationCodeAuthentication) throws OAuth2AuthenticationException { - - // Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest - // scope - // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. - // If the openid scope value is not present, the behavior is entirely unspecified. - if (authorizationCodeAuthentication.getAuthorizationRequest().getScope().contains("openid")) { - // The OpenID Connect implementation of AuthorizationGrantAuthenticator - // should handle OpenID Connect Authentication Requests - return null; - } - - TokenResponse tokenResponse = - this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication); - - AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(), - tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(), - tokenResponse.getExpiresAt(), tokenResponse.getScope()); - - OAuth2ClientAuthenticationToken clientAuthentication = - new OAuth2ClientAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), accessToken); - clientAuthentication.setDetails(authorizationCodeAuthentication.getDetails()); - - return clientAuthentication; - } -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationGrantAuthenticator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationGrantAuthenticator.java deleted file mode 100644 index dac55f077a..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationGrantAuthenticator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.client.authentication; - -/** - * A strategy used for "authenticating" an authorization grant credential - * with the authorization server's Token Endpoint. - * - * @author Joe Grandja - * @since 5.0 - */ -public interface AuthorizationGrantAuthenticator { - - OAuth2ClientAuthenticationToken authenticate(T authorizationGrantAuthentication) throws OAuth2AuthenticationException; - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DelegatingAuthorizationGrantAuthenticator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DelegatingAuthorizationGrantAuthenticator.java deleted file mode 100644 index c866bba17d..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DelegatingAuthorizationGrantAuthenticator.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.client.authentication; - -import org.springframework.core.ResolvableType; -import org.springframework.util.Assert; - -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * An implementation of an {@link AuthorizationGrantAuthenticator} that - * simply delegates to one of the {@link AuthorizationGrantAuthenticator}'s that it composes. - * - * @author Joe Grandja - * @since 5.0 - */ -public class DelegatingAuthorizationGrantAuthenticator implements AuthorizationGrantAuthenticator { - private final Map, List>> authenticators = new HashMap<>(); - - public DelegatingAuthorizationGrantAuthenticator(List> authenticators) { - Assert.notEmpty(authenticators, "authenticators cannot be empty"); - authenticators.forEach(authenticator -> { - Class authenticationType = - ResolvableType.forInstance(authenticator).as(AuthorizationGrantAuthenticator.class) - .resolveGeneric(0).asSubclass(AuthorizationGrantAuthenticationToken.class); - this.authenticators - .computeIfAbsent(authenticationType, k -> new LinkedList<>()) - .add(authenticator); - }); - } - - @Override - public OAuth2ClientAuthenticationToken authenticate(T authorizationGrantAuthentication) throws OAuth2AuthenticationException { - return this.authenticators.getOrDefault(authorizationGrantAuthentication.getClass(), Collections.emptyList()) - .stream() - .map(authenticator -> authenticator.authenticate(authorizationGrantAuthentication)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - } -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java similarity index 54% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticator.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java index 35386f8005..5ecea6a905 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticator.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticationProvider.java @@ -15,16 +15,22 @@ */ package org.springframework.security.oauth2.oidc.client.authentication; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.jwt.Jwt; import org.springframework.security.jwt.JwtDecoder; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.authentication.AuthorizationGrantAuthenticator; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; -import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry; 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; +import org.springframework.security.oauth2.core.endpoint.AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.TokenResponse; import org.springframework.security.oauth2.oidc.core.IdToken; import org.springframework.security.oauth2.oidc.core.OidcScope; @@ -32,22 +38,30 @@ import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter; import org.springframework.util.Assert; /** - * An implementation of an {@link AuthorizationGrantAuthenticator} that - * "authenticates" an authorization code grant credential - * against an OpenID Connect 1.0 Provider's Token Endpoint. + * An implementation of an {@link AuthenticationProvider} + * for the OpenID Connect Core 1.0 Authorization Code Grant Flow. + * + * This {@link AuthenticationProvider} is responsible for authenticating + * an authorization code credential with the authorization server's Token Endpoint + * and if valid, exchanging it for an access token credential. * * @author Joe Grandja * @since 5.0 - * @see AuthorizationGrantAuthenticator * @see AuthorizationCodeAuthenticationToken - * @see AuthorizationGrantTokenExchanger - * @see JwtDecoderRegistry + * @see OidcClientAuthenticationToken + * @see SecurityTokenRepository + * @see Section 3.1 Authorization Code Grant Flow + * @see Section 3.1.3.1 Token Request + * @see Section 3.1.3.3 Token Response */ -public class OidcAuthorizationCodeAuthenticator implements AuthorizationGrantAuthenticator { +public class OidcAuthorizationCodeAuthenticationProvider implements AuthenticationProvider { + private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; + private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; private final AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger; private final JwtDecoderRegistry jwtDecoderRegistry; + private SecurityTokenRepository accessTokenRepository = new InMemoryAccessTokenRepository(); - public OidcAuthorizationCodeAuthenticator( + public OidcAuthorizationCodeAuthenticationProvider( AuthorizationGrantTokenExchanger authorizationCodeTokenExchanger, JwtDecoderRegistry jwtDecoderRegistry) { @@ -58,18 +72,36 @@ public class OidcAuthorizationCodeAuthenticator implements AuthorizationGrantAut } @Override - public OAuth2ClientAuthenticationToken authenticate( - AuthorizationCodeAuthenticationToken authorizationCodeAuthentication) throws OAuth2AuthenticationException { + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = + (AuthorizationCodeAuthenticationToken) authentication; // Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest // scope // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. // If the openid scope value is not present, the behavior is entirely unspecified. if (!authorizationCodeAuthentication.getAuthorizationRequest().getScope().contains(OidcScope.OPENID)) { + // Let the standard OAuth 2.0 Authorization Code AuthenticationProvider handle this return null; } - ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration(); + AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationRequest(); + AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationResponse(); + + if (authorizationResponse.statusError()) { + throw new OAuth2AuthenticationException( + authorizationResponse.getError(), authorizationResponse.getError().toString()); + } + + if (!authorizationResponse.getState().equals(authorizationRequest.getState())) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + + if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } TokenResponse tokenResponse = this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication); @@ -78,6 +110,8 @@ public class OidcAuthorizationCodeAuthenticator implements AuthorizationGrantAut tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(), tokenResponse.getExpiresAt(), tokenResponse.getScope()); + ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration(); + if (!tokenResponse.getAdditionalParameters().containsKey(OidcParameter.ID_TOKEN)) { throw new IllegalArgumentException( "Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId()); @@ -95,6 +129,20 @@ public class OidcAuthorizationCodeAuthenticator implements AuthorizationGrantAut new OidcClientAuthenticationToken(clientRegistration, accessToken, idToken); clientAuthentication.setDetails(authorizationCodeAuthentication.getDetails()); + this.accessTokenRepository.saveSecurityToken( + clientAuthentication.getAccessToken(), + clientAuthentication.getClientRegistration()); + return clientAuthentication; } + + public final void setAccessTokenRepository(SecurityTokenRepository accessTokenRepository) { + Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null"); + this.accessTokenRepository = accessTokenRepository; + } + + @Override + public boolean supports(Class authentication) { + return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication); + } }