Skip to content

Commit e617236

Browse files
committed
Improve customizing OIDC UserInfo endpoint
Closes spring-projectsgh-785
1 parent 9c964e3 commit e617236

File tree

4 files changed

+329
-26
lines changed

4 files changed

+329
-26
lines changed

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

+101-5
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
1717

18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.function.Consumer;
1821
import java.util.function.Function;
1922

2023
import org.springframework.http.HttpMethod;
2124
import org.springframework.security.authentication.AuthenticationManager;
25+
import org.springframework.security.authentication.AuthenticationProvider;
2226
import org.springframework.security.config.annotation.ObjectPostProcessor;
2327
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2428
import org.springframework.security.oauth2.core.OAuth2AccessToken;
29+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
30+
import org.springframework.security.oauth2.core.OAuth2Error;
2531
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
2632
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
2733
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
@@ -30,21 +36,29 @@
3036
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
3137
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
3238
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
39+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
40+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
3341
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3442
import org.springframework.security.web.util.matcher.OrRequestMatcher;
3543
import org.springframework.security.web.util.matcher.RequestMatcher;
44+
import org.springframework.util.Assert;
3645

3746
/**
3847
* Configurer for OpenID Connect 1.0 UserInfo Endpoint.
3948
*
4049
* @author Steve Riesenberg
50+
* @author Daniel Garnier-Moiroux
4151
* @since 0.2.1
4252
* @see OidcConfigurer#userInfoEndpoint
4353
* @see OidcUserInfoEndpointFilter
4454
*/
4555
public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
4656
private RequestMatcher requestMatcher;
4757
private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper;
58+
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
59+
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
60+
private AuthenticationSuccessHandler userInfoResponseHandler;
61+
private AuthenticationFailureHandler errorResponseHandler;
4862

4963
/**
5064
* Restrict for internal use only.
@@ -74,6 +88,61 @@ public OidcUserInfoEndpointConfigurer userInfoMapper(Function<OidcUserInfoAuthen
7488
return this;
7589
}
7690

91+
/**
92+
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OidcUserInfoAuthenticationToken}.
93+
*
94+
* @param authenticationProvider a {@link AuthenticationProvider} used for authenticating a type of {@link OidcUserInfoAuthenticationToken}
95+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
96+
* @since 0.4.0
97+
*/
98+
public OidcUserInfoEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
99+
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
100+
this.authenticationProviders.add(authenticationProvider);
101+
return this;
102+
}
103+
104+
/**
105+
* Sets the {@code Consumer} providing access to the {@code List} of default
106+
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
107+
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
108+
*
109+
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
110+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
111+
* @since 0.4.0
112+
*/
113+
public OidcUserInfoEndpointConfigurer authenticationProviders(
114+
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
115+
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
116+
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
117+
return this;
118+
}
119+
120+
/**
121+
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcUserInfoAuthenticationToken} and
122+
* returning the {@link OidcUserInfo User Info Response}.
123+
*
124+
* @param userInfoResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OidcUserInfoAuthenticationToken}
125+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
126+
* @since 0.4.0
127+
*/
128+
public OidcUserInfoEndpointConfigurer userInfoResponseHandler(AuthenticationSuccessHandler userInfoResponseHandler) {
129+
this.userInfoResponseHandler = userInfoResponseHandler;
130+
return this;
131+
}
132+
133+
/**
134+
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} and
135+
* returning the {@link OAuth2Error Error Response}.
136+
*
137+
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
138+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
139+
* @since 0.4.0
140+
*/
141+
public OidcUserInfoEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
142+
this.errorResponseHandler = errorResponseHandler;
143+
return null;
144+
}
145+
77146
@Override
78147
void init(HttpSecurity httpSecurity) {
79148
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
@@ -82,13 +151,24 @@ void init(HttpSecurity httpSecurity) {
82151
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
83152
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
84153

85-
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider =
86-
new OidcUserInfoAuthenticationProvider(
87-
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
154+
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
155+
88156
if (this.userInfoMapper != null) {
89-
oidcUserInfoAuthenticationProvider.setUserInfoMapper(this.userInfoMapper);
157+
// @formatter:off
158+
authenticationProviders.stream()
159+
.filter(OidcUserInfoAuthenticationProvider.class::isInstance)
160+
.map(OidcUserInfoAuthenticationProvider.class::cast)
161+
.forEach(provider -> provider.setUserInfoMapper(this.userInfoMapper));
162+
// @formatter:on
90163
}
91-
httpSecurity.authenticationProvider(postProcess(oidcUserInfoAuthenticationProvider));
164+
165+
if (!this.authenticationProviders.isEmpty()) {
166+
authenticationProviders.addAll(0, this.authenticationProviders);
167+
}
168+
this.authenticationProvidersConsumer.accept(authenticationProviders);
169+
170+
authenticationProviders.forEach(authenticationProvider ->
171+
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
92172
}
93173

94174
@Override
@@ -100,6 +180,12 @@ void configure(HttpSecurity httpSecurity) {
100180
new OidcUserInfoEndpointFilter(
101181
authenticationManager,
102182
authorizationServerSettings.getOidcUserInfoEndpoint());
183+
if (this.userInfoResponseHandler != null) {
184+
oidcUserInfoEndpointFilter.setAuthenticationSuccessHandler(this.userInfoResponseHandler);
185+
}
186+
if (this.errorResponseHandler != null) {
187+
oidcUserInfoEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
188+
}
103189
httpSecurity.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), FilterSecurityInterceptor.class);
104190
}
105191

@@ -108,4 +194,14 @@ RequestMatcher getRequestMatcher() {
108194
return this.requestMatcher;
109195
}
110196

197+
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
198+
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
199+
200+
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider = new OidcUserInfoAuthenticationProvider(
201+
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
202+
authenticationProviders.add(oidcUserInfoAuthenticationProvider);
203+
204+
return authenticationProviders;
205+
}
206+
111207
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java

+40-7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.http.server.ServletServerHttpResponse;
2929
import org.springframework.security.authentication.AuthenticationManager;
3030
import org.springframework.security.core.Authentication;
31+
import org.springframework.security.core.AuthenticationException;
3132
import org.springframework.security.core.context.SecurityContextHolder;
3233
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3334
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -36,6 +37,8 @@
3637
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
3738
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
3839
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcUserInfoHttpMessageConverter;
40+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
41+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
3942
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4043
import org.springframework.security.web.util.matcher.OrRequestMatcher;
4144
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -66,6 +69,9 @@ public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter {
6669
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
6770
new OAuth2ErrorHttpMessageConverter();
6871

72+
private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess;
73+
private AuthenticationFailureHandler authenticationFailureHandler = this::onAuthenticationFailure;
74+
6975
/**
7076
* Constructs an {@code OidcUserInfoEndpointFilter} using the provided parameters.
7177
*
@@ -107,27 +113,53 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
107113
OidcUserInfoAuthenticationToken userInfoAuthenticationResult =
108114
(OidcUserInfoAuthenticationToken) this.authenticationManager.authenticate(userInfoAuthentication);
109115

110-
sendUserInfoResponse(response, userInfoAuthenticationResult.getUserInfo());
111-
116+
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, userInfoAuthenticationResult);
112117
} catch (OAuth2AuthenticationException ex) {
113-
sendErrorResponse(response, ex.getError());
118+
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
114119
} catch (Exception ex) {
115120
OAuth2Error error = new OAuth2Error(
116121
OAuth2ErrorCodes.INVALID_REQUEST,
117122
"OpenID Connect 1.0 UserInfo Error: " + ex.getMessage(),
118123
"https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError");
119-
sendErrorResponse(response, error);
124+
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
125+
new OAuth2AuthenticationException(error));
120126
} finally {
121127
SecurityContextHolder.clearContext();
122128
}
123129
}
124130

125-
private void sendUserInfoResponse(HttpServletResponse response, OidcUserInfo userInfo) throws IOException {
131+
/**
132+
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcUserInfoAuthenticationToken} and
133+
* returning the {@link OidcUserInfo OIDC User Info}.
134+
*
135+
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} for handling an {@link OidcUserInfoAuthenticationToken}
136+
*/
137+
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
138+
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
139+
this.authenticationSuccessHandler = authenticationSuccessHandler;
140+
}
141+
142+
/**
143+
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
144+
* and returning the {@link OAuth2Error Error Response}.
145+
*
146+
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
147+
*/
148+
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
149+
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
150+
this.authenticationFailureHandler = authenticationFailureHandler;
151+
}
152+
153+
private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
154+
Authentication authentication) throws IOException {
155+
OidcUserInfoAuthenticationToken userInfoAuthenticationToken = (OidcUserInfoAuthenticationToken) authentication;
126156
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
127-
this.userInfoHttpMessageConverter.write(userInfo, null, httpResponse);
157+
this.userInfoHttpMessageConverter.write(userInfoAuthenticationToken.getUserInfo(), null, httpResponse);
128158
}
129159

130-
private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
160+
private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
161+
AuthenticationException authenticationException) throws IOException {
162+
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
131163
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
132164
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_TOKEN)) {
133165
httpStatus = HttpStatus.UNAUTHORIZED;
@@ -138,4 +170,5 @@ private void sendErrorResponse(HttpServletResponse response, OAuth2Error error)
138170
httpResponse.setStatusCode(httpStatus);
139171
this.errorHttpResponseConverter.write(error, null, httpResponse);
140172
}
173+
141174
}

0 commit comments

Comments
 (0)