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

Generalize AuthorizationCodeAuthenticationProvider

The AuthorizationCodeAuthenticationProvider implements part of the
Authorization Code Grant flow as defined in
OAuth 2.0 Authorization Framework and OpenID Connect Core 1.0.
The implementation needs to be de-coupled to allow for better re-use and readability.
This commit introduces the AuthorizationGrantAuthenticator and extracts logic from
AuthorizationCodeAuthenticationProvider and provides different implementations
for OAuth 2.0 and OpenID Connect 1.0.

This re-factor is part of the work required for Issue gh-4513
Joe Grandja 8 жил өмнө
parent
commit
f8a9077d5a

+ 17 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java

@@ -20,6 +20,9 @@ import org.springframework.security.config.annotation.web.configurers.AbstractAu
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
 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.DelegatingAuthorizationGrantAuthenticator;
 import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry;
 import org.springframework.security.oauth2.client.authentication.jwt.nimbus.NimbusJwtDecoderRegistry;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -34,6 +37,7 @@ import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExc
 import org.springframework.security.oauth2.client.web.nimbus.NimbusAuthorizationCodeTokenExchanger;
 import org.springframework.security.oauth2.core.AccessToken;
 import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizationCodeAuthenticator;
 import org.springframework.security.oauth2.oidc.client.user.OidcUserService;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
@@ -51,6 +55,7 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
 		AbstractAuthenticationFilterConfigurer<H, AuthorizationCodeAuthenticationFilterConfigurer<H, R>, AuthorizationCodeAuthenticationProcessingFilter> {
 
 	private R authorizationResponseMatcher;
+	private AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> authorizationCodeAuthenticator;
 	private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
 	private SecurityTokenRepository<AccessToken> accessTokenRepository;
 	private JwtDecoderRegistry jwtDecoderRegistry;
@@ -124,8 +129,7 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
 	@Override
 	public void init(H http) throws Exception {
 		AuthorizationCodeAuthenticationProvider authenticationProvider = new AuthorizationCodeAuthenticationProvider(
-			this.getAuthorizationCodeTokenExchanger(), this.getAccessTokenRepository(),
-			this.getJwtDecoderRegistry(), this.getUserInfoService());
+			this.getAuthorizationCodeAuthenticator(), this.getAccessTokenRepository(), this.getUserInfoService());
 		if (this.userAuthoritiesMapper != null) {
 			authenticationProvider.setAuthoritiesMapper(this.userAuthoritiesMapper);
 		}
@@ -150,6 +154,17 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
 			this.authorizationResponseMatcher : this.getAuthenticationFilter().getAuthorizationResponseMatcher());
 	}
 
+	private AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> getAuthorizationCodeAuthenticator() {
+		if (this.authorizationCodeAuthenticator == null) {
+			List<AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken>> 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<AuthorizationCodeAuthenticationToken> getAuthorizationCodeTokenExchanger() {
 		if (this.authorizationCodeTokenExchanger == null) {
 			this.authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger();

+ 28 - 67
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticationProvider.java

@@ -21,21 +21,12 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
-import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
-import org.springframework.security.jwt.Jwt;
-import org.springframework.security.jwt.JwtDecoder;
-import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry;
-import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.token.SecurityTokenRepository;
 import org.springframework.security.oauth2.client.user.OAuth2UserService;
-import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger;
 import org.springframework.security.oauth2.core.AccessToken;
-import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
 import org.springframework.security.oauth2.core.user.OAuth2User;
 import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken;
 import org.springframework.security.oauth2.oidc.client.authentication.OidcUserAuthenticationToken;
-import org.springframework.security.oauth2.oidc.core.IdToken;
-import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter;
 import org.springframework.security.oauth2.oidc.core.user.OidcUser;
 import org.springframework.util.Assert;
 
@@ -51,16 +42,13 @@ import java.util.Collection;
  * associating it with the returned {@link OAuth2UserAuthenticationToken}.
  *
  * <p>
- * The {@link AuthorizationCodeAuthenticationProvider} uses an {@link AuthorizationGrantTokenExchanger}
- * to make a request to the authorization server's <i>Token Endpoint</i>
- * to verify the {@link AuthorizationCodeAuthenticationToken#getAuthorizationCode()}.
- * If the request is valid, the authorization server will respond back with a {@link TokenResponseAttributes}.
+ * The {@link AuthorizationCodeAuthenticationProvider} uses an {@link AuthorizationGrantAuthenticator}
+ * to authenticate the {@link AuthorizationCodeAuthenticationToken#getAuthorizationCode()} and ultimately
+ * return an <i>&quot;Authorized Client&quot;</i> as an {@link OAuth2ClientAuthenticationToken}.
  *
  * <p>
- * It will then create an {@link OAuth2ClientAuthenticationToken} associating the {@link AccessToken} and optionally
- * the {@link IdToken} from the {@link TokenResponseAttributes} and pass it to
- * {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken)} to obtain the end-user's (resource owner) attributes
- * in the form of an {@link OAuth2User}.
+ * It will then call {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken)}
+ * to obtain the end-user's (resource owner) attributes in the form of an {@link OAuth2User}.
  *
  * <p>
  * Finally, it will create an {@link OAuth2UserAuthenticationToken}, associating the {@link OAuth2User}
@@ -73,10 +61,8 @@ import java.util.Collection;
  * @see OAuth2ClientAuthenticationToken
  * @see OidcClientAuthenticationToken
  * @see OAuth2UserAuthenticationToken
- * @see AuthorizationGrantTokenExchanger
- * @see TokenResponseAttributes
- * @see AccessToken
- * @see IdToken
+ * @see OidcUserAuthenticationToken
+ * @see AuthorizationGrantAuthenticator
  * @see OAuth2UserService
  * @see OAuth2User
  * @see OidcUser
@@ -87,76 +73,51 @@ import java.util.Collection;
  * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 OpenID Connect Token Response</a>
  */
 public class AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
-	private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
+	private final AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> authorizationCodeAuthenticator;
 	private final SecurityTokenRepository<AccessToken> accessTokenRepository;
-	private final JwtDecoderRegistry jwtDecoderRegistry;
-	private final OAuth2UserService userInfoService;
-	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
+	private final OAuth2UserService userService;
+	private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
 
 	public AuthorizationCodeAuthenticationProvider(
-			AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
+			AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> authorizationCodeAuthenticator,
 			SecurityTokenRepository<AccessToken> accessTokenRepository,
-			JwtDecoderRegistry jwtDecoderRegistry,
-			OAuth2UserService userInfoService) {
+			OAuth2UserService userService) {
 
-		Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
+		Assert.notNull(authorizationCodeAuthenticator, "authorizationCodeAuthenticator cannot be null");
 		Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null");
-		Assert.notNull(jwtDecoderRegistry, "jwtDecoderRegistry cannot be null");
-		Assert.notNull(userInfoService, "userInfoService cannot be null");
-		this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
+		Assert.notNull(userService, "userService cannot be null");
+		this.authorizationCodeAuthenticator = authorizationCodeAuthenticator;
 		this.accessTokenRepository = accessTokenRepository;
-		this.jwtDecoderRegistry = jwtDecoderRegistry;
-		this.userInfoService = userInfoService;
+		this.userService = userService;
 	}
 
 	@Override
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 		AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
 				(AuthorizationCodeAuthenticationToken) authentication;
-		ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
 
-		TokenResponseAttributes tokenResponse =
-				this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
+		OAuth2ClientAuthenticationToken oauth2ClientAuthentication =
+			this.authorizationCodeAuthenticator.authenticate(authorizationCodeAuthentication);
 
-		AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
-				tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
-				tokenResponse.getExpiresAt(), tokenResponse.getScope());
+		this.accessTokenRepository.saveSecurityToken(
+			oauth2ClientAuthentication.getAccessToken(),
+			oauth2ClientAuthentication.getClientRegistration());
 
-		IdToken idToken = null;
-		if (tokenResponse.getAdditionalParameters().containsKey(OidcParameter.ID_TOKEN)) {
-			JwtDecoder jwtDecoder = this.jwtDecoderRegistry.getJwtDecoder(clientRegistration);
-			if (jwtDecoder == null) {
-				throw new IllegalArgumentException("Unable to find a registered JwtDecoder for Client Registration: '" + clientRegistration.getRegistrationId() +
-					"'. Check to ensure you have configured the JwkSet URI property.");
-			}
-			Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN));
-			idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
-		}
-
-		OAuth2ClientAuthenticationToken oauth2ClientAuthentication;
-		if (idToken != null) {
-			oauth2ClientAuthentication = new OidcClientAuthenticationToken(clientRegistration, accessToken, idToken);
-		} else {
-			oauth2ClientAuthentication = new OAuth2ClientAuthenticationToken(clientRegistration, accessToken);
-		}
-		oauth2ClientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
-
-		OAuth2User user = this.userInfoService.loadUser(oauth2ClientAuthentication);
+		OAuth2User oauth2User = this.userService.loadUser(oauth2ClientAuthentication);
 
-		Collection<? extends GrantedAuthority> authorities =
-				this.authoritiesMapper.mapAuthorities(user.getAuthorities());
+		Collection<? extends GrantedAuthority> mappedAuthorities =
+				this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
 
 		OAuth2UserAuthenticationToken oauth2UserAuthentication;
-		if (OidcUser.class.isAssignableFrom(user.getClass())) {
+		if (OidcUser.class.isAssignableFrom(oauth2User.getClass())) {
 			oauth2UserAuthentication = new OidcUserAuthenticationToken(
-				(OidcUser)user, authorities, (OidcClientAuthenticationToken)oauth2ClientAuthentication);
+				(OidcUser)oauth2User, mappedAuthorities, (OidcClientAuthenticationToken)oauth2ClientAuthentication);
 		} else {
-			oauth2UserAuthentication = new OAuth2UserAuthenticationToken(user, authorities, oauth2ClientAuthentication);
+			oauth2UserAuthentication = new OAuth2UserAuthenticationToken(
+				oauth2User, mappedAuthorities, oauth2ClientAuthentication);
 		}
 		oauth2UserAuthentication.setDetails(oauth2ClientAuthentication.getDetails());
 
-		this.accessTokenRepository.saveSecurityToken(accessToken, clientRegistration);
-
 		return oauth2UserAuthentication;
 	}
 

+ 11 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticationToken.java

@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.authentication;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
 import org.springframework.util.Assert;
 
 /**
@@ -33,13 +34,18 @@ import org.springframework.util.Assert;
 public class AuthorizationCodeAuthenticationToken extends AuthorizationGrantAuthenticationToken {
 	private final String authorizationCode;
 	private final ClientRegistration clientRegistration;
+	private final AuthorizationRequestAttributes authorizationRequest;
 
-	public AuthorizationCodeAuthenticationToken(String authorizationCode, ClientRegistration clientRegistration) {
+	public AuthorizationCodeAuthenticationToken(String authorizationCode,
+												ClientRegistration clientRegistration,
+												AuthorizationRequestAttributes authorizationRequest) {
 		super(AuthorizationGrantType.AUTHORIZATION_CODE, AuthorityUtils.NO_AUTHORITIES);
 		Assert.hasText(authorizationCode, "authorizationCode cannot be empty");
 		Assert.notNull(clientRegistration, "clientRegistration cannot be null");
+		Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
 		this.authorizationCode = authorizationCode;
 		this.clientRegistration = clientRegistration;
+		this.authorizationRequest = authorizationRequest;
 		this.setAuthenticated(false);
 	}
 
@@ -60,4 +66,8 @@ public class AuthorizationCodeAuthenticationToken extends AuthorizationGrantAuth
 	public ClientRegistration getClientRegistration() {
 		return this.clientRegistration;
 	}
+
+	public AuthorizationRequestAttributes getAuthorizationRequest() {
+		return this.authorizationRequest;
+	}
 }

+ 66 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationCodeAuthenticator.java

@@ -0,0 +1,66 @@
+/*
+ * 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.client.web.AuthorizationGrantTokenExchanger;
+import org.springframework.security.oauth2.core.AccessToken;
+import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
+import org.springframework.util.Assert;
+
+/**
+ * An implementation of an {@link AuthorizationGrantAuthenticator} that
+ * <i>&quot;authenticates&quot;</i> an <i>authorization code grant</i> credential
+ * against an OAuth 2.0 Provider's <i>Token Endpoint</i>.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ */
+public class AuthorizationCodeAuthenticator implements AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> {
+	private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
+
+	public AuthorizationCodeAuthenticator(AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> 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
+			// must handle OpenID Connect Authentication Requests
+			return null;
+		}
+
+		TokenResponseAttributes tokenResponse =
+			this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
+
+		AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
+			tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
+			tokenResponse.getExpiresAt(), tokenResponse.getScope());
+
+		OAuth2ClientAuthenticationToken oauth2ClientAuthentication =
+			new OAuth2ClientAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), accessToken);
+		oauth2ClientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
+
+		return oauth2ClientAuthentication;
+	}
+}

+ 29 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/AuthorizationGrantAuthenticator.java

@@ -0,0 +1,29 @@
+/*
+ * 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 <i>&quot;authenticating&quot;</i> an <i>authorization grant</i> credential
+ * with the authorization server's <i>Token Endpoint</i>.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ */
+public interface AuthorizationGrantAuthenticator<T extends AuthorizationGrantAuthenticationToken> {
+
+	OAuth2ClientAuthenticationToken authenticate(T authorizationGrantAuthentication) throws OAuth2AuthenticationException;
+
+}

+ 59 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DelegatingAuthorizationGrantAuthenticator.java

@@ -0,0 +1,59 @@
+/*
+ * 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<T extends AuthorizationGrantAuthenticationToken> implements AuthorizationGrantAuthenticator<T> {
+	private final Map<Class<? extends AuthorizationGrantAuthenticationToken>, List<AuthorizationGrantAuthenticator<T>>> authenticators = new HashMap<>();
+
+	public DelegatingAuthorizationGrantAuthenticator(List<AuthorizationGrantAuthenticator<T>> authenticators) {
+		Assert.notEmpty(authenticators, "authenticators cannot be empty");
+		authenticators.forEach(authenticator -> {
+			Class<? extends AuthorizationGrantAuthenticationToken> 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);
+	}
+}

+ 6 - 40
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationProcessingFilter.java

@@ -21,21 +21,15 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider;
 import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
-import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
-import org.springframework.security.oauth2.client.authentication.OAuth2UserAuthenticationToken;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
-import org.springframework.security.oauth2.client.user.OAuth2UserService;
 import org.springframework.security.oauth2.client.web.converter.AuthorizationCodeAuthorizationResponseAttributesConverter;
 import org.springframework.security.oauth2.client.web.converter.ErrorResponseAttributesConverter;
-import org.springframework.security.oauth2.core.AccessToken;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.endpoint.AuthorizationCodeAuthorizationResponseAttributes;
 import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
 import org.springframework.security.oauth2.core.endpoint.ErrorResponseAttributes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
-import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
-import org.springframework.security.oauth2.core.user.OAuth2User;
 import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
@@ -51,9 +45,9 @@ import java.io.IOException;
  * the processing of an <i>OAuth 2.0 Authorization Response</i> for the authorization code grant flow.
  *
  * <p>
- * This <code>Filter</code> processes the <i>Authorization Response</i> in the following step sequence:
+ * This <code>Filter</code> processes the <i>Authorization Response</i> as follows:
  *
- * <ol>
+ * <ul>
  * <li>
  *	Assuming the resource owner (end-user) has granted access to the client, the authorization server will append the
  *	{@link OAuth2Parameter#CODE} and {@link OAuth2Parameter#STATE} (if provided in the <i>Authorization Request</i>) parameters
@@ -62,47 +56,19 @@ import java.io.IOException;
  * </li>
  * <li>
  *  This <code>Filter</code> will then create an {@link AuthorizationCodeAuthenticationToken} with
- *  the {@link OAuth2Parameter#CODE} received in the previous step and pass it to
+ *  the {@link OAuth2Parameter#CODE} received in the previous step and delegate it to
  *  {@link AuthorizationCodeAuthenticationProvider#authenticate(Authentication)} (indirectly via {@link AuthenticationManager}).
- *  The {@link AuthorizationCodeAuthenticationProvider} will use an {@link AuthorizationGrantTokenExchanger} to make a request
- *  to the authorization server's <i>Token Endpoint</i> for exchanging the {@link OAuth2Parameter#CODE} for an {@link AccessToken}.
  * </li>
- * <li>
- *  Upon receiving the <i>Access Token Request</i>, the authorization server will authenticate the client,
- *  verify the {@link OAuth2Parameter#CODE}, and ensure that the {@link OAuth2Parameter#REDIRECT_URI}
- *	received matches the <code>URI</code> originally provided in the <i>Authorization Request</i>.
- *	If the request is valid, the authorization server will respond back with a {@link TokenResponseAttributes}.
- * </li>
- * <li>
- *  The {@link AuthorizationCodeAuthenticationProvider} will then create a new {@link OAuth2ClientAuthenticationToken}
- *  associating the {@link AccessToken} from the {@link TokenResponseAttributes} and pass it to
- *  {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken)}. The {@link OAuth2UserService} will make a request
- *  to the authorization server's <i>UserInfo Endpoint</i> (using the {@link AccessToken})
- *  to obtain the end-user's (resource owner) attributes and return it in the form of an {@link OAuth2User}.
- * </li>
- * <li>
- *  The {@link AuthorizationCodeAuthenticationProvider} will then create a {@link OAuth2UserAuthenticationToken}
- *  associating the {@link OAuth2ClientAuthenticationToken} and {@link OAuth2User} returned from the {@link OAuth2UserService}.
- *  Finally, the {@link OAuth2UserAuthenticationToken} is returned to the {@link AuthenticationManager}
- *  and then back to this <code>Filter</code> at which point the session is considered <i>&quot;authenticated&quot;</i>.
- * </li>
- * </ol>
- *
- * <p>
- * <b>NOTE:</b> Steps 4-5 are <b>not</b> part of the authorization code grant flow and instead are
- * <i>&quot;authentication flow&quot;</i> steps that are required in order to authenticate the end-user with the system.
+ * </ul>
  *
  * @author Joe Grandja
  * @since 5.0
  * @see AbstractAuthenticationProcessingFilter
  * @see AuthorizationCodeAuthenticationToken
  * @see AuthorizationCodeAuthenticationProvider
- * @see AuthorizationGrantTokenExchanger
- * @see AuthorizationCodeAuthorizationResponseAttributes
+ * @see AuthorizationCodeRequestRedirectFilter
  * @see AuthorizationRequestAttributes
  * @see AuthorizationRequestRepository
- * @see AuthorizationCodeRequestRedirectFilter
- * @see ClientRegistration
  * @see ClientRegistrationRepository
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
@@ -154,7 +120,7 @@ public class AuthorizationCodeAuthenticationProcessingFilter extends AbstractAut
 				this.authorizationCodeResponseConverter.apply(request);
 
 		AuthorizationCodeAuthenticationToken authRequest = new AuthorizationCodeAuthenticationToken(
-				authorizationCodeResponseAttributes.getCode(), clientRegistration);
+				authorizationCodeResponseAttributes.getCode(), clientRegistration, matchingAuthorizationRequest);
 
 		authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
 

+ 95 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/authentication/OidcAuthorizationCodeAuthenticator.java

@@ -0,0 +1,95 @@
+/*
+ * 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.oidc.client.authentication;
+
+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.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.web.AuthorizationGrantTokenExchanger;
+import org.springframework.security.oauth2.core.AccessToken;
+import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
+import org.springframework.security.oauth2.oidc.core.IdToken;
+import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter;
+import org.springframework.util.Assert;
+
+/**
+ * An implementation of an {@link AuthorizationGrantAuthenticator} that
+ * <i>&quot;authenticates&quot;</i> an <i>authorization code grant</i> credential
+ * against an OpenID Connect 1.0 Provider's <i>Token Endpoint</i>.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ */
+public class OidcAuthorizationCodeAuthenticator implements AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> {
+	private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
+	private final JwtDecoderRegistry jwtDecoderRegistry;
+
+	public OidcAuthorizationCodeAuthenticator(
+		AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
+		JwtDecoderRegistry jwtDecoderRegistry) {
+
+		Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
+		Assert.notNull(jwtDecoderRegistry, "jwtDecoderRegistry cannot be null");
+		this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
+		this.jwtDecoderRegistry = jwtDecoderRegistry;
+	}
+
+	@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")) {
+			return null;
+		}
+
+		ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
+
+		TokenResponseAttributes tokenResponse =
+			this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
+
+		AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
+			tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
+			tokenResponse.getExpiresAt(), tokenResponse.getScope());
+
+		if (!tokenResponse.getAdditionalParameters().containsKey(OidcParameter.ID_TOKEN)) {
+			throw new IllegalArgumentException(
+				"Missing (required) ID Token in Token Response for Client Registration: '" + clientRegistration.getRegistrationId() + "'");
+		}
+
+		JwtDecoder jwtDecoder = this.jwtDecoderRegistry.getJwtDecoder(clientRegistration);
+		if (jwtDecoder == null) {
+			throw new IllegalArgumentException("Unable to find a registered JwtDecoder for Client Registration: '" + clientRegistration.getRegistrationId() +
+				"'. Check to ensure you have configured the JwkSet URI.");
+		}
+		Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN));
+		IdToken idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
+
+		OidcClientAuthenticationToken oidcClientAuthentication =
+			new OidcClientAuthenticationToken(clientRegistration, accessToken, idToken);
+		oidcClientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
+
+		return oidcClientAuthentication;
+	}
+}