Skip to content

Commit 40b38ff

Browse files
author
Steve Riesenberg
committed
Implement User Info Endpoint
1 parent c9da687 commit 40b38ff

File tree

16 files changed

+1720
-151
lines changed

16 files changed

+1720
-151
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
* @since 0.2.0
3737
* @see OAuth2AuthorizationServerConfigurer#oidc
3838
* @see OidcClientRegistrationEndpointConfigurer
39+
* @see OidcUserInfoEndpointConfigurer
3940
* @see OidcProviderConfigurationEndpointFilter
4041
*/
4142
public final class OidcConfigurer extends AbstractOAuth2Configurer {
43+
private final OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer;
4244
private OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer;
4345
private RequestMatcher requestMatcher;
4446

@@ -47,6 +49,7 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
4749
*/
4850
OidcConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
4951
super(objectPostProcessor);
52+
this.userInfoEndpointConfigurer = new OidcUserInfoEndpointConfigurer(objectPostProcessor);
5053
}
5154

5255
/**
@@ -63,8 +66,20 @@ public OidcConfigurer clientRegistrationEndpoint(Customizer<OidcClientRegistrati
6366
return this;
6467
}
6568

69+
/**
70+
* Configures the OpenID Connect 1.0 UserInfo Endpoint.
71+
*
72+
* @param userInfoEndpointCustomizer the {@link Customizer} providing access to the {@link OidcUserInfoEndpointConfigurer}
73+
* @return the {@link OidcConfigurer} for further configuration
74+
*/
75+
public OidcConfigurer userInfoEndpoint(Customizer<OidcUserInfoEndpointConfigurer> userInfoEndpointCustomizer) {
76+
userInfoEndpointCustomizer.customize(this.userInfoEndpointConfigurer);
77+
return this;
78+
}
79+
6680
@Override
6781
<B extends HttpSecurityBuilder<B>> void init(B builder) {
82+
this.userInfoEndpointConfigurer.init(builder);
6883
if (this.clientRegistrationEndpointConfigurer != null) {
6984
this.clientRegistrationEndpointConfigurer.init(builder);
7085
}
@@ -75,14 +90,16 @@ <B extends HttpSecurityBuilder<B>> void init(B builder) {
7590
requestMatchers.add(new AntPathRequestMatcher(
7691
"/.well-known/openid-configuration", HttpMethod.GET.name()));
7792
}
93+
requestMatchers.add(this.userInfoEndpointConfigurer.getRequestMatcher());
7894
if (this.clientRegistrationEndpointConfigurer != null) {
7995
requestMatchers.add(this.clientRegistrationEndpointConfigurer.getRequestMatcher());
8096
}
81-
this.requestMatcher = !requestMatchers.isEmpty() ? new OrRequestMatcher(requestMatchers) : request -> false;
97+
this.requestMatcher = requestMatchers.size() > 1 ? new OrRequestMatcher(requestMatchers) : requestMatchers.get(0);
8298
}
8399

84100
@Override
85101
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
102+
this.userInfoEndpointConfigurer.configure(builder);
86103
if (this.clientRegistrationEndpointConfigurer != null) {
87104
this.clientRegistrationEndpointConfigurer.configure(builder);
88105
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2020-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
17+
18+
import java.util.function.Function;
19+
20+
import org.springframework.http.HttpMethod;
21+
import org.springframework.security.authentication.AuthenticationManager;
22+
import org.springframework.security.config.annotation.ObjectPostProcessor;
23+
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
24+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
25+
import org.springframework.security.oauth2.core.OAuth2Token;
26+
import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
27+
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
28+
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
29+
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
30+
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
31+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
32+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
33+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
34+
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
35+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
36+
import org.springframework.security.web.util.matcher.OrRequestMatcher;
37+
import org.springframework.security.web.util.matcher.RequestMatcher;
38+
39+
/**
40+
* Configurer for OpenID Connect 1.0 UserInfo Endpoint.
41+
*
42+
* @author Steve Riesenberg
43+
* @since 0.2.1
44+
* @see OidcConfigurer#userInfoEndpoint
45+
* @see OidcUserInfoEndpointFilter
46+
*/
47+
public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
48+
private RequestMatcher requestMatcher;
49+
private Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper;
50+
51+
/**
52+
* Restrict for internal use only.
53+
*/
54+
OidcUserInfoEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
55+
super(objectPostProcessor);
56+
}
57+
58+
/**
59+
* Sets the {@link Function} used to extract claims from an {@link OAuth2AuthenticationContext}
60+
* to an instance of {@link OidcUserInfo}.
61+
*
62+
* <p>
63+
* The {@link OAuth2AuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken}.
64+
* In addition, the following context attributes are supported:
65+
* <ul>
66+
* <li>{@code OAuth2Token.class} - The {@link OAuth2Token} containing the bearer token used to make the request.</li>
67+
* <li>{@code OAuth2Authorization.class} - The {@link OAuth2Authorization} containing the {@link OidcIdToken} and
68+
* {@link OAuth2AccessToken} associated with the bearer token used to make the request.</li>
69+
* </ul>
70+
*
71+
* @param userInfoMapper the {@link Function} used to extract claims from an {@link OAuth2AuthenticationContext} to an instance of {@link OidcUserInfo}
72+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
73+
*/
74+
public OidcUserInfoEndpointConfigurer userInfoMapper(Function<OAuth2AuthenticationContext, OidcUserInfo> userInfoMapper) {
75+
this.userInfoMapper = userInfoMapper;
76+
return this;
77+
}
78+
79+
@Override
80+
<B extends HttpSecurityBuilder<B>> void init(B builder) {
81+
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
82+
String userInfoEndpointUri = providerSettings.getOidcUserInfoEndpoint();
83+
this.requestMatcher = new OrRequestMatcher(
84+
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
85+
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
86+
87+
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider =
88+
new OidcUserInfoAuthenticationProvider(
89+
OAuth2ConfigurerUtils.getAuthorizationService(builder));
90+
if (this.userInfoMapper != null) {
91+
oidcUserInfoAuthenticationProvider.setUserInfoMapper(this.userInfoMapper);
92+
}
93+
builder.authenticationProvider(postProcess(oidcUserInfoAuthenticationProvider));
94+
}
95+
96+
@Override
97+
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
98+
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
99+
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
100+
101+
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
102+
new OidcUserInfoEndpointFilter(
103+
authenticationManager,
104+
providerSettings.getOidcUserInfoEndpoint());
105+
builder.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), FilterSecurityInterceptor.class);
106+
}
107+
108+
@Override
109+
RequestMatcher getRequestMatcher() {
110+
return this.requestMatcher;
111+
}
112+
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcUserInfoHttpMessageConverter.java

+56-49
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 the original author or authors.
2+
* Copyright 2020-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,44 +15,46 @@
1515
*/
1616
package org.springframework.security.oauth2.core.oidc.http.converter;
1717

18+
import java.time.Instant;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
1822
import org.springframework.core.ParameterizedTypeReference;
1923
import org.springframework.core.convert.TypeDescriptor;
2024
import org.springframework.core.convert.converter.Converter;
2125
import org.springframework.http.HttpInputMessage;
2226
import org.springframework.http.HttpOutputMessage;
2327
import org.springframework.http.MediaType;
24-
import org.springframework.http.converter.HttpMessageConverter;
28+
import org.springframework.http.converter.AbstractHttpMessageConverter;
2529
import org.springframework.http.converter.GenericHttpMessageConverter;
26-
import org.springframework.http.converter.HttpMessageNotWritableException;
30+
import org.springframework.http.converter.HttpMessageConverter;
2731
import org.springframework.http.converter.HttpMessageNotReadableException;
28-
import org.springframework.http.converter.AbstractHttpMessageConverter;
32+
import org.springframework.http.converter.HttpMessageNotWritableException;
2933
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
3034
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
3135
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
3236
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
3337
import org.springframework.util.Assert;
3438

35-
import java.util.HashMap;
36-
import java.util.Map;
37-
3839
/**
39-
* A {@link HttpMessageConverter} for an {@link OidcUserInfo OIDC User Info Response}.
40+
* A {@link HttpMessageConverter} for an {@link OidcUserInfo OpenID Connect UserInfo Request and Response}.
4041
*
4142
* @author Ido Salomon
43+
* @author Steve Riesenberg
44+
* @since 0.2.1
4245
* @see AbstractHttpMessageConverter
4346
* @see OidcUserInfo
44-
* @since 0.1.1
4547
*/
4648
public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConverter<OidcUserInfo> {
4749

4850
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
49-
new ParameterizedTypeReference<Map<String, Object>>() {
50-
};
51+
new ParameterizedTypeReference<Map<String, Object>>() {};
5152

52-
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
53+
private final GenericHttpMessageConverter<Object> jsonMessageConverter =
54+
HttpMessageConverters.getJsonMessageConverter();
5355

54-
private Converter<Map<String, Object>, OidcUserInfo> oidcUserInfoConverter = new OidcUserInfoConverter();
55-
private Converter<OidcUserInfo, Map<String, Object>> oidcUserInfoParametersConverter = OidcUserInfo::getClaims;
56+
private Converter<Map<String, Object>, OidcUserInfo> userInfoConverter = new MapOidcUserInfoConverter();
57+
private Converter<OidcUserInfo, Map<String, Object>> userInfoParametersConverter = OidcUserInfo::getClaims;
5658

5759
public OidcUserInfoHttpMessageConverter() {
5860
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
@@ -68,90 +70,95 @@ protected boolean supports(Class<?> clazz) {
6870
protected OidcUserInfo readInternal(Class<? extends OidcUserInfo> clazz, HttpInputMessage inputMessage)
6971
throws HttpMessageNotReadableException {
7072
try {
71-
Map<String, Object> oidcUserInfoParameters =
73+
Map<String, Object> userInfoParameters =
7274
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
73-
return this.oidcUserInfoConverter.convert(oidcUserInfoParameters);
75+
return this.userInfoConverter.convert(userInfoParameters);
7476
} catch (Exception ex) {
7577
throw new HttpMessageNotReadableException(
76-
"An error occurred reading the OIDC User Info: " + ex.getMessage(), ex, inputMessage);
78+
"An error occurred reading the UserInfo: " + ex.getMessage(), ex, inputMessage);
7779
}
7880
}
7981

8082
@Override
8183
protected void writeInternal(OidcUserInfo oidcUserInfo, HttpOutputMessage outputMessage)
8284
throws HttpMessageNotWritableException {
8385
try {
84-
Map<String, Object> oidcUserInfoResponseParameters =
85-
this.oidcUserInfoParametersConverter.convert(oidcUserInfo);
86+
Map<String, Object> userInfoResponseParameters =
87+
this.userInfoParametersConverter.convert(oidcUserInfo);
8688
this.jsonMessageConverter.write(
87-
oidcUserInfoResponseParameters,
89+
userInfoResponseParameters,
8890
STRING_OBJECT_MAP.getType(),
8991
MediaType.APPLICATION_JSON,
9092
outputMessage
9193
);
9294
} catch (Exception ex) {
9395
throw new HttpMessageNotWritableException(
94-
"An error occurred writing the OIDC User Info response: " + ex.getMessage(), ex);
96+
"An error occurred writing the UserInfo response: " + ex.getMessage(), ex);
9597
}
9698
}
9799

98100
/**
99-
* Sets the {@link Converter} used for converting the OIDC User Info parameters
101+
* Sets the {@link Converter} used for converting the UserInfo parameters
100102
* to an {@link OidcUserInfo}.
101103
*
102-
* @param oidcUserInfoConverter the {@link Converter} used for converting to an
103-
* {@link OidcUserInfo}
104+
* @param userInfoConverter the {@link Converter} used for converting to an
105+
* {@link OidcUserInfo}
104106
*/
105-
public final void setOidcUserInfoConverter(Converter<Map<String, Object>, OidcUserInfo> oidcUserInfoConverter) {
106-
Assert.notNull(oidcUserInfoConverter, "oidcUserInfoConverter cannot be null");
107-
this.oidcUserInfoConverter = oidcUserInfoConverter;
107+
public final void setUserInfoConverter(Converter<Map<String, Object>, OidcUserInfo> userInfoConverter) {
108+
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null");
109+
this.userInfoConverter = userInfoConverter;
108110
}
109111

110112
/**
111113
* Sets the {@link Converter} used for converting the {@link OidcUserInfo} to a
112-
* {@code Map} representation of the OIDC User Info.
114+
* {@code Map} representation of the UserInfo.
113115
*
114-
* @param oidcUserInfoParametersConverter the {@link Converter} used for converting to a
115-
* {@code Map} representation of the OIDC User Info
116+
* @param userInfoParametersConverter the {@link Converter} used for converting to a
117+
* {@code Map} representation of the UserInfo
116118
*/
117-
public final void setOidcUserInfoParametersConverter(
118-
Converter<OidcUserInfo, Map<String, Object>> oidcUserInfoParametersConverter) {
119-
Assert.notNull(oidcUserInfoParametersConverter, "oidcUserInfoParametersConverter cannot be null");
120-
this.oidcUserInfoParametersConverter = oidcUserInfoParametersConverter;
119+
public final void setUserInfoParametersConverter(
120+
Converter<OidcUserInfo, Map<String, Object>> userInfoParametersConverter) {
121+
Assert.notNull(userInfoParametersConverter, "userInfoParametersConverter cannot be null");
122+
this.userInfoParametersConverter = userInfoParametersConverter;
121123
}
122124

123-
private static final class OidcUserInfoConverter implements Converter<Map<String, Object>, OidcUserInfo> {
125+
private static final class MapOidcUserInfoConverter implements Converter<Map<String, Object>, OidcUserInfo> {
126+
124127
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
125128
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
126129
private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
127130
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
131+
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
132+
private static final TypeDescriptor STRING_OBJECT_MAP_DESCRIPTOR = TypeDescriptor.map(Map.class, STRING_TYPE_DESCRIPTOR, OBJECT_TYPE_DESCRIPTOR);
128133
private final ClaimTypeConverter claimTypeConverter;
129134

130-
private OidcUserInfoConverter() {
131-
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
135+
private MapOidcUserInfoConverter() {
132136
Converter<Object, ?> booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR);
137+
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
138+
Converter<Object, ?> instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR);
139+
Converter<Object, ?> mapConverter = getConverter(STRING_OBJECT_MAP_DESCRIPTOR);
133140

134141
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
135142
claimConverters.put(StandardClaimNames.SUB, stringConverter);
136-
claimConverters.put(StandardClaimNames.PROFILE, stringConverter);
137-
claimConverters.put(StandardClaimNames.ADDRESS, stringConverter);
138-
claimConverters.put(StandardClaimNames.BIRTHDATE, stringConverter);
139-
claimConverters.put(StandardClaimNames.EMAIL, stringConverter);
140-
claimConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
141143
claimConverters.put(StandardClaimNames.NAME, stringConverter);
142144
claimConverters.put(StandardClaimNames.GIVEN_NAME, stringConverter);
143-
claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter);
144145
claimConverters.put(StandardClaimNames.FAMILY_NAME, stringConverter);
146+
claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter);
145147
claimConverters.put(StandardClaimNames.NICKNAME, stringConverter);
146148
claimConverters.put(StandardClaimNames.PREFERRED_USERNAME, stringConverter);
147-
claimConverters.put(StandardClaimNames.LOCALE, stringConverter);
148-
claimConverters.put(StandardClaimNames.GENDER, stringConverter);
149-
claimConverters.put(StandardClaimNames.PHONE_NUMBER, stringConverter);
150-
claimConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, stringConverter);
149+
claimConverters.put(StandardClaimNames.PROFILE, stringConverter);
151150
claimConverters.put(StandardClaimNames.PICTURE, stringConverter);
152-
claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter);
153151
claimConverters.put(StandardClaimNames.WEBSITE, stringConverter);
154-
claimConverters.put(StandardClaimNames.UPDATED_AT, stringConverter);
152+
claimConverters.put(StandardClaimNames.EMAIL, stringConverter);
153+
claimConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
154+
claimConverters.put(StandardClaimNames.GENDER, stringConverter);
155+
claimConverters.put(StandardClaimNames.BIRTHDATE, stringConverter);
156+
claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter);
157+
claimConverters.put(StandardClaimNames.LOCALE, stringConverter);
158+
claimConverters.put(StandardClaimNames.PHONE_NUMBER, stringConverter);
159+
claimConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
160+
claimConverters.put(StandardClaimNames.ADDRESS, mapConverter);
161+
claimConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
155162

156163
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
157164
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ConfigurationSettingNames.java

+5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ public static final class Provider {
9494
*/
9595
public static final String OIDC_CLIENT_REGISTRATION_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("oidc-client-registration-endpoint");
9696

97+
/**
98+
* Set the Provider's OpenID Connect 1.0 UserInfo endpoint.
99+
*/
100+
public static final String OIDC_USER_INFO_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("oidc-user-info-endpoint");
101+
97102
private Provider() {
98103
}
99104

0 commit comments

Comments
 (0)