Skip to content

Commit 2407755

Browse files
author
Steve Riesenberg
committed
Implement User Info Endpoint
1 parent 9945cae commit 2407755

File tree

18 files changed

+1637
-138
lines changed

18 files changed

+1637
-138
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @see OidcProviderConfigurationEndpointFilter
4040
*/
4141
public final class OidcConfigurer extends AbstractOAuth2Configurer {
42+
private final OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer;
4243
private OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer;
4344
private RequestMatcher requestMatcher;
4445

@@ -47,6 +48,7 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
4748
*/
4849
OidcConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
4950
super(objectPostProcessor);
51+
this.userInfoEndpointConfigurer = new OidcUserInfoEndpointConfigurer(objectPostProcessor);
5052
}
5153

5254
/**
@@ -63,8 +65,20 @@ public OidcConfigurer clientRegistrationEndpoint(Customizer<OidcClientRegistrati
6365
return this;
6466
}
6567

68+
/**
69+
* Configures the OAuth 2.0 Protected Resource UserInfo Endpoint.
70+
*
71+
* @param userInfoEndpointCustomizer the {@link Customizer} providing access to the {@link OidcUserInfoEndpointConfigurer}
72+
* @return the {@link OidcConfigurer} for further configuration
73+
*/
74+
public OidcConfigurer userInfoEndpoint(Customizer<OidcUserInfoEndpointConfigurer> userInfoEndpointCustomizer) {
75+
userInfoEndpointCustomizer.customize(this.userInfoEndpointConfigurer);
76+
return this;
77+
}
78+
6679
@Override
6780
<B extends HttpSecurityBuilder<B>> void init(B builder) {
81+
this.userInfoEndpointConfigurer.init(builder);
6882
if (this.clientRegistrationEndpointConfigurer != null) {
6983
this.clientRegistrationEndpointConfigurer.init(builder);
7084
}
@@ -75,14 +89,16 @@ <B extends HttpSecurityBuilder<B>> void init(B builder) {
7589
requestMatchers.add(new AntPathRequestMatcher(
7690
"/.well-known/openid-configuration", HttpMethod.GET.name()));
7791
}
92+
requestMatchers.add(this.userInfoEndpointConfigurer.getRequestMatcher());
7893
if (this.clientRegistrationEndpointConfigurer != null) {
7994
requestMatchers.add(this.clientRegistrationEndpointConfigurer.getRequestMatcher());
8095
}
81-
this.requestMatcher = !requestMatchers.isEmpty() ? new OrRequestMatcher(requestMatchers) : request -> false;
96+
this.requestMatcher = requestMatchers.size() > 1 ? new OrRequestMatcher(requestMatchers) : requestMatchers.get(0);
8297
}
8398

8499
@Override
85100
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
101+
this.userInfoEndpointConfigurer.configure(builder);
86102
if (this.clientRegistrationEndpointConfigurer != null) {
87103
this.clientRegistrationEndpointConfigurer.configure(builder);
88104
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 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 javax.servlet.http.HttpServletRequest;
21+
22+
import org.springframework.http.HttpMethod;
23+
import org.springframework.security.authentication.AuthenticationManager;
24+
import org.springframework.security.config.annotation.ObjectPostProcessor;
25+
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
26+
import org.springframework.security.core.Authentication;
27+
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
28+
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
29+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
30+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
31+
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
32+
import org.springframework.security.web.authentication.AuthenticationConverter;
33+
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
34+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
35+
import org.springframework.security.web.util.matcher.OrRequestMatcher;
36+
import org.springframework.security.web.util.matcher.RequestMatcher;
37+
38+
/**
39+
* Configurer for OAuth 2.0 Protected Resource UserInfo Endpoint.
40+
*
41+
* @author Steve Riesenberg
42+
* @since 0.2.1
43+
* @see OidcConfigurer#userInfoEndpoint
44+
* @see OidcUserInfoEndpointFilter
45+
*/
46+
public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
47+
private RequestMatcher requestMatcher;
48+
private AuthenticationConverter authenticationConverter;
49+
private Function<Authentication, 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 AuthenticationConverter} used when attempting to extract a bearer token from {@link HttpServletRequest}
60+
* to an instance of {@link OidcUserInfoAuthenticationToken} used for authenticating the request.
61+
*
62+
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a bearer token from {@link HttpServletRequest}
63+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
64+
*/
65+
public OidcUserInfoEndpointConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
66+
this.authenticationConverter = authenticationConverter;
67+
return this;
68+
}
69+
70+
/**
71+
* Sets the {@link Function} used to extract claims from an {@link Authentication}
72+
* to an instance of {@link OidcUserInfo}.
73+
*
74+
* @param userInfoMapper the {@link Function} used to extract claims from an {@link Authentication} to an instance of {@link OidcUserInfo}
75+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
76+
*/
77+
public OidcUserInfoEndpointConfigurer userInfoMapper(Function<Authentication, OidcUserInfo> userInfoMapper) {
78+
this.userInfoMapper = userInfoMapper;
79+
return this;
80+
}
81+
82+
@Override
83+
<B extends HttpSecurityBuilder<B>> void init(B builder) {
84+
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
85+
String userInfoEndpointUri = providerSettings.getOidcUserInfoEndpoint();
86+
this.requestMatcher = new OrRequestMatcher(
87+
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
88+
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
89+
90+
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider =
91+
new OidcUserInfoAuthenticationProvider(
92+
OAuth2ConfigurerUtils.getAuthorizationService(builder));
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+
if (this.authenticationConverter != null) {
106+
oidcUserInfoEndpointFilter.setAuthenticationConverter(this.authenticationConverter);
107+
}
108+
if (this.userInfoMapper != null) {
109+
oidcUserInfoEndpointFilter.setUserInfoMapper(this.userInfoMapper);
110+
}
111+
builder.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
112+
}
113+
114+
@Override
115+
RequestMatcher getRequestMatcher() {
116+
return this.requestMatcher;
117+
}
118+
}

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

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 the original author or authors.
2+
* Copyright 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,45 @@
1515
*/
1616
package org.springframework.security.oauth2.core.oidc.http.converter;
1717

18+
import java.util.HashMap;
19+
import java.util.Map;
20+
1821
import org.springframework.core.ParameterizedTypeReference;
1922
import org.springframework.core.convert.TypeDescriptor;
2023
import org.springframework.core.convert.converter.Converter;
2124
import org.springframework.http.HttpInputMessage;
2225
import org.springframework.http.HttpOutputMessage;
2326
import org.springframework.http.MediaType;
24-
import org.springframework.http.converter.HttpMessageConverter;
27+
import org.springframework.http.converter.AbstractHttpMessageConverter;
2528
import org.springframework.http.converter.GenericHttpMessageConverter;
26-
import org.springframework.http.converter.HttpMessageNotWritableException;
29+
import org.springframework.http.converter.HttpMessageConverter;
2730
import org.springframework.http.converter.HttpMessageNotReadableException;
28-
import org.springframework.http.converter.AbstractHttpMessageConverter;
31+
import org.springframework.http.converter.HttpMessageNotWritableException;
2932
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
3033
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
3134
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
3235
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
3336
import org.springframework.util.Assert;
3437

35-
import java.util.HashMap;
36-
import java.util.Map;
37-
3838
/**
39-
* A {@link HttpMessageConverter} for an {@link OidcUserInfo OIDC User Info Response}.
39+
* A {@link HttpMessageConverter} for an {@link OidcUserInfo OAuth 2.0 Protected Resource UserInfo Response}.
4040
*
4141
* @author Ido Salomon
42+
* @author Steve Riesenberg
43+
* @since 0.2.1
4244
* @see AbstractHttpMessageConverter
4345
* @see OidcUserInfo
44-
* @since 0.1.1
4546
*/
4647
public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConverter<OidcUserInfo> {
4748

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

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

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

5758
public OidcUserInfoHttpMessageConverter() {
5859
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
@@ -68,89 +69,91 @@ protected boolean supports(Class<?> clazz) {
6869
protected OidcUserInfo readInternal(Class<? extends OidcUserInfo> clazz, HttpInputMessage inputMessage)
6970
throws HttpMessageNotReadableException {
7071
try {
71-
Map<String, Object> oidcUserInfoParameters =
72+
Map<String, Object> userInfoParameters =
7273
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
73-
return this.oidcUserInfoConverter.convert(oidcUserInfoParameters);
74+
return this.userInfoConverter.convert(userInfoParameters);
7475
} catch (Exception ex) {
7576
throw new HttpMessageNotReadableException(
76-
"An error occurred reading the OIDC User Info: " + ex.getMessage(), ex, inputMessage);
77+
"An error occurred reading the UserInfo: " + ex.getMessage(), ex, inputMessage);
7778
}
7879
}
7980

8081
@Override
8182
protected void writeInternal(OidcUserInfo oidcUserInfo, HttpOutputMessage outputMessage)
8283
throws HttpMessageNotWritableException {
8384
try {
84-
Map<String, Object> oidcUserInfoResponseParameters =
85-
this.oidcUserInfoParametersConverter.convert(oidcUserInfo);
85+
Map<String, Object> userInfoResponseParameters =
86+
this.userInfoParametersConverter.convert(oidcUserInfo);
8687
this.jsonMessageConverter.write(
87-
oidcUserInfoResponseParameters,
88+
userInfoResponseParameters,
8889
STRING_OBJECT_MAP.getType(),
8990
MediaType.APPLICATION_JSON,
9091
outputMessage
9192
);
9293
} catch (Exception ex) {
9394
throw new HttpMessageNotWritableException(
94-
"An error occurred writing the OIDC User Info response: " + ex.getMessage(), ex);
95+
"An error occurred writing the OAuth 2.0 Protected Resource UserInfo response: " + ex.getMessage(), ex);
9596
}
9697
}
9798

9899
/**
99-
* Sets the {@link Converter} used for converting the OIDC User Info parameters
100+
* Sets the {@link Converter} used for converting the UserInfo parameters
100101
* to an {@link OidcUserInfo}.
101102
*
102-
* @param oidcUserInfoConverter the {@link Converter} used for converting to an
103-
* {@link OidcUserInfo}
103+
* @param userInfoConverter the {@link Converter} used for converting to an
104+
* {@link OidcUserInfo}
104105
*/
105-
public final void setOidcUserInfoConverter(Converter<Map<String, Object>, OidcUserInfo> oidcUserInfoConverter) {
106-
Assert.notNull(oidcUserInfoConverter, "oidcUserInfoConverter cannot be null");
107-
this.oidcUserInfoConverter = oidcUserInfoConverter;
106+
public final void setUserInfoConverter(Converter<Map<String, Object>, OidcUserInfo> userInfoConverter) {
107+
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null");
108+
this.userInfoConverter = userInfoConverter;
108109
}
109110

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

123124
private static final class OidcUserInfoConverter implements Converter<Map<String, Object>, OidcUserInfo> {
124125
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
125126
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
126127
private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
127128
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
129+
private static final TypeDescriptor STRING_OBJECT_MAP_DESCRIPTOR = TypeDescriptor.map(Map.class, STRING_TYPE_DESCRIPTOR, OBJECT_TYPE_DESCRIPTOR);
128130
private final ClaimTypeConverter claimTypeConverter;
129131

130132
private OidcUserInfoConverter() {
131133
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
132134
Converter<Object, ?> booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR);
135+
Converter<Object, ?> mapConverter = getConverter(STRING_OBJECT_MAP_DESCRIPTOR);
133136

134137
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
135138
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);
141139
claimConverters.put(StandardClaimNames.NAME, stringConverter);
142140
claimConverters.put(StandardClaimNames.GIVEN_NAME, stringConverter);
143-
claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter);
144141
claimConverters.put(StandardClaimNames.FAMILY_NAME, stringConverter);
142+
claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter);
145143
claimConverters.put(StandardClaimNames.NICKNAME, stringConverter);
146144
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);
145+
claimConverters.put(StandardClaimNames.PROFILE, stringConverter);
151146
claimConverters.put(StandardClaimNames.PICTURE, stringConverter);
152-
claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter);
153147
claimConverters.put(StandardClaimNames.WEBSITE, stringConverter);
148+
claimConverters.put(StandardClaimNames.EMAIL, stringConverter);
149+
claimConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
150+
claimConverters.put(StandardClaimNames.GENDER, stringConverter);
151+
claimConverters.put(StandardClaimNames.BIRTHDATE, stringConverter);
152+
claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter);
153+
claimConverters.put(StandardClaimNames.LOCALE, stringConverter);
154+
claimConverters.put(StandardClaimNames.PHONE_NUMBER, stringConverter);
155+
claimConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
156+
claimConverters.put(StandardClaimNames.ADDRESS, mapConverter);
154157
claimConverters.put(StandardClaimNames.UPDATED_AT, stringConverter);
155158

156159
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);

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

Lines changed: 5 additions & 0 deletions
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 OAuth 2.0 Protected Resource 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)