소스 검색

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 년 전
부모
커밋
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;
+	}
+}