Browse Source

Introduce OidcUserInfoAuthenticationContext

Issue gh-441
Joe Grandja 3 years ago
parent
commit
82e4f3a345

+ 9 - 11
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoEndpointConfigurer.java

@@ -22,12 +22,10 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
-import org.springframework.security.oauth2.core.OAuth2Token;
-import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
 import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
-import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
@@ -46,7 +44,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
  */
 public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
 	private RequestMatcher requestMatcher;
-	private Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper;
+	private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper;
 
 	/**
 	 * Restrict for internal use only.
@@ -56,22 +54,22 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
 	}
 
 	/**
-	 * Sets the {@link Function} used to extract claims from an {@link OAuth2AuthenticationContext}
+	 * Sets the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext}
 	 * to an instance of {@link OidcUserInfo} for the UserInfo response.
 	 *
 	 * <p>
-	 * The {@link OAuth2AuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken}.
-	 * In addition, the following context attributes are supported:
+	 * The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken},
+	 * as well as, the following context attributes:
 	 * <ul>
-	 * <li>{@code OAuth2Token.class} - The {@link OAuth2Token} containing the bearer token used to make the request.</li>
-	 * <li>{@code OAuth2Authorization.class} - The {@link OAuth2Authorization} containing the {@link OidcIdToken} and
+	 * <li>{@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the bearer token used to make the request.</li>
+	 * <li>{@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the {@link OidcIdToken} and
 	 * {@link OAuth2AccessToken} associated with the bearer token used to make the request.</li>
 	 * </ul>
 	 *
-	 * @param userInfoMapper the {@link Function} used to extract claims from an {@link OAuth2AuthenticationContext} to an instance of {@link OidcUserInfo}
+	 * @param userInfoMapper the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo}
 	 * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
 	 */
-	public OidcUserInfoEndpointConfigurer userInfoMapper(Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper) {
+	public OidcUserInfoEndpointConfigurer userInfoMapper(Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper) {
 		this.userInfoMapper = userInfoMapper;
 		return this;
 	}

+ 112 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java

@@ -0,0 +1,112 @@
+/*
+ * Copyright 2020-2021 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
+ *
+ *      https://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.server.authorization.oidc.authentication;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
+import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link OAuth2AuthenticationContext} that holds an {@link OidcUserInfoAuthenticationToken} and additional information
+ * and is used when mapping claims to an instance of {@link OidcUserInfo}.
+ *
+ * @author Joe Grandja
+ * @since 0.2.1
+ * @see OAuth2AuthenticationContext
+ * @see OidcUserInfoAuthenticationProvider#setUserInfoMapper(Function)
+ */
+public final class OidcUserInfoAuthenticationContext extends OAuth2AuthenticationContext {
+
+	private OidcUserInfoAuthenticationContext(Map<Object, Object> context) {
+		super(context);
+	}
+
+	/**
+	 * Returns the {@link OAuth2AccessToken OAuth 2.0 Access Token}.
+	 *
+	 * @return the {@link OAuth2AccessToken}
+	 */
+	public OAuth2AccessToken getAccessToken() {
+		return get(OAuth2AccessToken.class);
+	}
+
+	/**
+	 * Returns the {@link OAuth2Authorization authorization}.
+	 *
+	 * @return the {@link OAuth2Authorization}
+	 */
+	public OAuth2Authorization getAuthorization() {
+		return get(OAuth2Authorization.class);
+	}
+
+	/**
+	 * Constructs a new {@link Builder} with the provided {@link OidcUserInfoAuthenticationToken}.
+	 *
+	 * @param authentication the {@link OidcUserInfoAuthenticationToken}
+	 * @return the {@link Builder}
+	 */
+	public static Builder with(OidcUserInfoAuthenticationToken authentication) {
+		return new Builder(authentication);
+	}
+
+	/**
+	 * A builder for {@link OidcUserInfoAuthenticationContext}.
+	 */
+	public static final class Builder extends AbstractBuilder<OidcUserInfoAuthenticationContext, Builder> {
+
+		private Builder(OidcUserInfoAuthenticationToken authentication) {
+			super(authentication);
+		}
+
+		/**
+		 * Sets the {@link OAuth2AccessToken OAuth 2.0 Access Token}.
+		 *
+		 * @param accessToken the {@link OAuth2AccessToken}
+		 * @return the {@link Builder} for further configuration
+		 */
+		public Builder accessToken(OAuth2AccessToken accessToken) {
+			return put(OAuth2AccessToken.class, accessToken);
+		}
+
+		/**
+		 * Sets the {@link OAuth2Authorization authorization}.
+		 *
+		 * @param authorization the {@link OAuth2Authorization}
+		 * @return the {@link Builder} for further configuration
+		 */
+		public Builder authorization(OAuth2Authorization authorization) {
+			return put(OAuth2Authorization.class, authorization);
+		}
+
+		/**
+		 * Builds a new {@link OidcUserInfoAuthenticationContext}.
+		 *
+		 * @return the {@link OidcUserInfoAuthenticationContext}
+		 */
+		public OidcUserInfoAuthenticationContext build() {
+			Assert.notNull(get(OAuth2AccessToken.class), "accessToken cannot be null");
+			Assert.notNull(get(OAuth2Authorization.class), "authorization cannot be null");
+			return new OidcUserInfoAuthenticationContext(getContext());
+		}
+
+	}
+
+}

+ 17 - 20
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java

@@ -29,9 +29,7 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
-import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2TokenType;
-import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
 import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
@@ -51,7 +49,7 @@ import org.springframework.util.Assert;
  */
 public final class OidcUserInfoAuthenticationProvider implements AuthenticationProvider {
 	private final OAuth2AuthorizationService authorizationService;
-	private Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper = new DefaultOidcUserInfoMapper();
+	private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = new DefaultOidcUserInfoMapper();
 
 	/**
 	 * Constructs an {@code OidcUserInfoAuthenticationProvider} using the provided parameters.
@@ -98,12 +96,11 @@ public final class OidcUserInfoAuthenticationProvider implements AuthenticationP
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
 		}
 
-		Map<Object, Object> context = new HashMap<>();
-		context.put(OAuth2Token.class, accessTokenAuthentication.getToken());
-		context.put(OAuth2Authorization.class, authorization);
-		OAuth2AuthenticationContext authenticationContext = new OAuth2AuthenticationContext(
-				userInfoAuthentication, context);
-
+		OidcUserInfoAuthenticationContext authenticationContext =
+				OidcUserInfoAuthenticationContext.with(userInfoAuthentication)
+						.accessToken(authorizedAccessToken.getToken())
+						.authorization(authorization)
+						.build();
 		OidcUserInfo userInfo = this.userInfoMapper.apply(authenticationContext);
 
 		return new OidcUserInfoAuthenticationToken(accessTokenAuthentication, userInfo);
@@ -115,26 +112,26 @@ public final class OidcUserInfoAuthenticationProvider implements AuthenticationP
 	}
 
 	/**
-	 * Sets the {@link Function} used to extract claims from an {@link OAuth2AuthenticationContext}
+	 * Sets the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext}
 	 * to an instance of {@link OidcUserInfo} for the UserInfo response.
 	 *
 	 * <p>
-	 * The {@link OAuth2AuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken}.
-	 * In addition, the following context attributes are supported:
+	 * The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken},
+	 * as well as, the following context attributes:
 	 * <ul>
-	 * <li>{@code OAuth2Token.class} - The {@link OAuth2Token} containing the bearer token used to make the request.</li>
-	 * <li>{@code OAuth2Authorization.class} - The {@link OAuth2Authorization} containing the {@link OidcIdToken} and
+	 * <li>{@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the bearer token used to make the request.</li>
+	 * <li>{@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the {@link OidcIdToken} and
 	 * {@link OAuth2AccessToken} associated with the bearer token used to make the request.</li>
 	 * </ul>
 	 *
-	 * @param userInfoMapper the {@link Function} used to extract claims from an {@link OAuth2AuthenticationContext} to an instance of {@link OidcUserInfo}
+	 * @param userInfoMapper the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo}
 	 */
-	public void setUserInfoMapper(Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper) {
+	public void setUserInfoMapper(Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper) {
 		Assert.notNull(userInfoMapper, "userInfoMapper cannot be null");
 		this.userInfoMapper = userInfoMapper;
 	}
 
-	private static final class DefaultOidcUserInfoMapper implements Function<OAuth2AuthenticationContext, OidcUserInfo> {
+	private static final class DefaultOidcUserInfoMapper implements Function<OidcUserInfoAuthenticationContext, OidcUserInfo> {
 
 		private static final List<String> EMAIL_CLAIMS = Arrays.asList(
 				StandardClaimNames.EMAIL,
@@ -162,10 +159,10 @@ public final class OidcUserInfoAuthenticationProvider implements AuthenticationP
 		);
 
 		@Override
-		public OidcUserInfo apply(OAuth2AuthenticationContext authenticationContext) {
-			OAuth2Authorization authorization = authenticationContext.get(OAuth2Authorization.class);
+		public OidcUserInfo apply(OidcUserInfoAuthenticationContext authenticationContext) {
+			OAuth2Authorization authorization = authenticationContext.getAuthorization();
 			OidcIdToken idToken = authorization.getToken(OidcIdToken.class).getToken();
-			OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
+			OAuth2AccessToken accessToken = authenticationContext.getAccessToken();
 			Map<String, Object> scopeRequestedClaims = getClaimsRequestedByScope(idToken.getClaims(),
 					accessToken.getScopes());
 

+ 2 - 2
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java

@@ -38,7 +38,6 @@ import org.springframework.security.config.annotation.web.configuration.OAuth2Au
 import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
 import org.springframework.security.config.test.SpringTestRule;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
-import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
 import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
@@ -59,6 +58,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
 import org.springframework.security.web.SecurityFilterChain;
@@ -230,7 +230,7 @@ public class OidcUserInfoTests {
 					.getEndpointsMatcher();
 
 			// Custom User Info Mapper that retrieves claims from a signed JWT
-			Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper = context -> {
+			Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = context -> {
 				OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
 				JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();