diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoEndpointConfigurer.java index d718c700..7fec8089 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoEndpointConfigurer.java +++ b/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 userInfoMapper; + private Function 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. * *

- * 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: *

* - * @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 userInfoMapper) { + public OidcUserInfoEndpointConfigurer userInfoMapper(Function userInfoMapper) { this.userInfoMapper = userInfoMapper; return this; } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java new file mode 100644 index 00000000..d3954dac --- /dev/null +++ b/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 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 { + + 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()); + } + + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java index e47b6a78..104da524 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java +++ b/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 userInfoMapper = new DefaultOidcUserInfoMapper(); + private Function 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 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. * *

- * 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: *

    - *
  • {@code OAuth2Token.class} - The {@link OAuth2Token} containing the bearer token used to make the request.
  • - *
  • {@code OAuth2Authorization.class} - The {@link OAuth2Authorization} containing the {@link OidcIdToken} and + *
  • {@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the bearer token used to make the request.
  • + *
  • {@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the {@link OidcIdToken} and * {@link OAuth2AccessToken} associated with the bearer token used to make the request.
  • *
* - * @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 userInfoMapper) { + public void setUserInfoMapper(Function userInfoMapper) { Assert.notNull(userInfoMapper, "userInfoMapper cannot be null"); this.userInfoMapper = userInfoMapper; } - private static final class DefaultOidcUserInfoMapper implements Function { + private static final class DefaultOidcUserInfoMapper implements Function { private static final List 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 scopeRequestedClaims = getClaimsRequestedByScope(idToken.getClaims(), accessToken.getScopes()); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java index 5210bcc4..b1569f96 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java +++ b/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 userInfoMapper = context -> { + Function userInfoMapper = context -> { OidcUserInfoAuthenticationToken authentication = context.getAuthentication(); JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();