Skip to content

Commit c872499

Browse files
committed
Enable custom configuration for HTTP client
Fixes gh-4477
1 parent 3b42323 commit c872499

File tree

5 files changed

+154
-11
lines changed

5 files changed

+154
-11
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java

+31-9
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*/
1616
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
1717

18+
import org.springframework.context.ApplicationContext;
1819
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
1920
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
2021
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
22+
import org.springframework.security.jose.jws.JwsAlgorithm;
2123
import org.springframework.security.jwt.JwtDecoder;
2224
import org.springframework.security.jwt.nimbus.NimbusJwtDecoderJwkSupport;
2325
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter;
@@ -31,6 +33,7 @@
3133
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
3234
import org.springframework.security.oauth2.client.user.OAuth2UserService;
3335
import org.springframework.security.oauth2.client.user.nimbus.NimbusOAuth2UserService;
36+
import org.springframework.security.oauth2.core.http.HttpClientConfig;
3437
import org.springframework.security.oauth2.core.provider.DefaultProviderMetadata;
3538
import org.springframework.security.oauth2.core.provider.ProviderMetadata;
3639
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -113,7 +116,7 @@ String getLoginFailureUrl() {
113116
@Override
114117
public void init(H http) throws Exception {
115118
AuthorizationCodeAuthenticationProvider authenticationProvider = new AuthorizationCodeAuthenticationProvider(
116-
this.getAuthorizationCodeTokenExchanger(), this.getProviderJwtDecoderRegistry(), this.getUserInfoService());
119+
this.getAuthorizationCodeTokenExchanger(http), this.getProviderJwtDecoderRegistry(http), this.getUserInfoService(http));
117120
if (this.userAuthoritiesMapper != null) {
118121
authenticationProvider.setAuthoritiesMapper(this.userAuthoritiesMapper);
119122
}
@@ -134,14 +137,20 @@ protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingU
134137
return this.getAuthenticationFilter().getAuthorizeRequestMatcher();
135138
}
136139

137-
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> getAuthorizationCodeTokenExchanger() {
140+
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> getAuthorizationCodeTokenExchanger(H http) {
138141
if (this.authorizationCodeTokenExchanger == null) {
139-
this.authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger();
142+
NimbusAuthorizationCodeTokenExchanger nimbusAuthorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger();
143+
HttpClientConfig httpClientConfig = this.getHttpClientConfig(http);
144+
if (httpClientConfig != null) {
145+
nimbusAuthorizationCodeTokenExchanger.setHttpClientConfig(httpClientConfig);
146+
}
147+
this.authorizationCodeTokenExchanger = nimbusAuthorizationCodeTokenExchanger;
140148
}
141149
return this.authorizationCodeTokenExchanger;
142150
}
143151

144-
private ProviderJwtDecoderRegistry getProviderJwtDecoderRegistry() {
152+
private ProviderJwtDecoderRegistry getProviderJwtDecoderRegistry(H http) {
153+
HttpClientConfig httpClientConfig = this.getHttpClientConfig(http);
145154
Map<ProviderMetadata, JwtDecoder> jwtDecoders = new HashMap<>();
146155
ClientRegistrationRepository clientRegistrationRepository = OAuth2LoginConfigurer.getClientRegistrationRepository(this.getBuilder());
147156
clientRegistrationRepository.getRegistrations().stream().forEach(registration -> {
@@ -159,25 +168,38 @@ private ProviderJwtDecoderRegistry getProviderJwtDecoderRegistry() {
159168
providerMetadata.setTokenEndpoint(this.toURL(providerDetails.getTokenUri()));
160169
providerMetadata.setUserInfoEndpoint(this.toURL(providerDetails.getUserInfoUri()));
161170
providerMetadata.setJwkSetUri(this.toURL(providerDetails.getJwkSetUri()));
162-
jwtDecoders.put(providerMetadata, new NimbusJwtDecoderJwkSupport(providerDetails.getJwkSetUri()));
171+
NimbusJwtDecoderJwkSupport nimbusJwtDecoderJwkSupport = new NimbusJwtDecoderJwkSupport(
172+
providerDetails.getJwkSetUri(), JwsAlgorithm.RS256, httpClientConfig);
173+
jwtDecoders.put(providerMetadata, nimbusJwtDecoderJwkSupport);
163174
}
164175
});
165176
return new DefaultProviderJwtDecoderRegistry(jwtDecoders);
166177
}
167178

168-
private OAuth2UserService getUserInfoService() {
179+
private OAuth2UserService getUserInfoService(H http) {
169180
if (this.userInfoService == null) {
170-
this.userInfoService = new NimbusOAuth2UserService();
181+
NimbusOAuth2UserService nimbusOAuth2UserService = new NimbusOAuth2UserService();
171182
if (!this.customUserTypes.isEmpty()) {
172-
((NimbusOAuth2UserService)this.userInfoService).setCustomUserTypes(this.customUserTypes);
183+
nimbusOAuth2UserService.setCustomUserTypes(this.customUserTypes);
173184
}
174185
if (!this.userNameAttributeNames.isEmpty()) {
175-
((NimbusOAuth2UserService)this.userInfoService).setUserNameAttributeNames(this.userNameAttributeNames);
186+
nimbusOAuth2UserService.setUserNameAttributeNames(this.userNameAttributeNames);
187+
}
188+
HttpClientConfig httpClientConfig = this.getHttpClientConfig(http);
189+
if (httpClientConfig != null) {
190+
nimbusOAuth2UserService.setHttpClientConfig(httpClientConfig);
176191
}
192+
this.userInfoService = nimbusOAuth2UserService;
177193
}
178194
return this.userInfoService;
179195
}
180196

197+
private HttpClientConfig getHttpClientConfig(H http) {
198+
Map<String, HttpClientConfig> httpClientConfigs =
199+
http.getSharedObject(ApplicationContext.class).getBeansOfType(HttpClientConfig.class);
200+
return (!httpClientConfigs.isEmpty() ? httpClientConfigs.values().iterator().next() : null);
201+
}
202+
181203
private URL toURL(String urlStr) {
182204
if (!StringUtils.hasText(urlStr)) {
183205
return null;

oauth2/jwt-jose/src/main/java/org/springframework/security/jwt/nimbus/NimbusJwtDecoderJwkSupport.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.nimbusds.jose.proc.JWSKeySelector;
2222
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
2323
import com.nimbusds.jose.proc.SecurityContext;
24+
import com.nimbusds.jose.util.DefaultResourceRetriever;
25+
import com.nimbusds.jose.util.ResourceRetriever;
2426
import com.nimbusds.jwt.JWT;
2527
import com.nimbusds.jwt.JWTClaimsSet;
2628
import com.nimbusds.jwt.JWTParser;
@@ -30,6 +32,7 @@
3032
import org.springframework.security.jwt.Jwt;
3133
import org.springframework.security.jwt.JwtDecoder;
3234
import org.springframework.security.jwt.JwtException;
35+
import org.springframework.security.oauth2.core.http.HttpClientConfig;
3336
import org.springframework.util.Assert;
3437

3538
import java.net.MalformedURLException;
@@ -65,6 +68,10 @@ public NimbusJwtDecoderJwkSupport(String jwkSetUrl) {
6568
}
6669

6770
public NimbusJwtDecoderJwkSupport(String jwkSetUrl, String jwsAlgorithm) {
71+
this(jwkSetUrl, jwsAlgorithm, null);
72+
}
73+
74+
public NimbusJwtDecoderJwkSupport(String jwkSetUrl, String jwsAlgorithm, HttpClientConfig httpClientConfig) {
6875
Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty");
6976
Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
7077
try {
@@ -74,10 +81,16 @@ public NimbusJwtDecoderJwkSupport(String jwkSetUrl, String jwsAlgorithm) {
7481
}
7582
this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
7683

77-
this.jwtProcessor = new DefaultJWTProcessor<>();
78-
JWKSource jwkSource = new RemoteJWKSet(this.jwkSetUrl);
84+
int connectTimeout = (httpClientConfig != null ?
85+
httpClientConfig.getConnectTimeout() : HttpClientConfig.DEFAULT_CONNECT_TIMEOUT);
86+
int readTimeout = (httpClientConfig != null ?
87+
httpClientConfig.getReadTimeout() : HttpClientConfig.DEFAULT_READ_TIMEOUT);
88+
ResourceRetriever jwkSetRetriever = new DefaultResourceRetriever(connectTimeout, readTimeout);
89+
JWKSource jwkSource = new RemoteJWKSet(this.jwkSetUrl, jwkSetRetriever);
7990
JWSKeySelector<SecurityContext> jwsKeySelector =
8091
new JWSVerificationKeySelector<SecurityContext>(this.jwsAlgorithm, jwkSource);
92+
93+
this.jwtProcessor = new DefaultJWTProcessor<>();
8194
this.jwtProcessor.setJWSKeySelector(jwsKeySelector);
8295
}
8396

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/nimbus/NimbusAuthorizationCodeTokenExchanger.java

+10
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
2929
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
3030
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
31+
import org.springframework.security.oauth2.core.http.HttpClientConfig;
3132
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3233
import org.springframework.security.oauth2.core.AccessToken;
3334
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
3435
import org.springframework.security.oauth2.core.OAuth2Error;
3536
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
37+
import org.springframework.util.Assert;
3638
import org.springframework.util.CollectionUtils;
3739

3840
import java.io.IOException;
@@ -61,6 +63,7 @@
6163
*/
6264
public class NimbusAuthorizationCodeTokenExchanger implements AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> {
6365
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
66+
private HttpClientConfig httpClientConfig = new HttpClientConfig();
6467

6568
@Override
6669
public TokenResponseAttributes exchange(AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken)
@@ -90,6 +93,8 @@ public TokenResponseAttributes exchange(AuthorizationCodeAuthenticationToken aut
9093
TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant);
9194
HTTPRequest httpRequest = tokenRequest.toHTTPRequest();
9295
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
96+
httpRequest.setConnectTimeout(this.httpClientConfig.getConnectTimeout());
97+
httpRequest.setReadTimeout(this.httpClientConfig.getReadTimeout());
9398
tokenResponse = TokenResponse.parse(httpRequest.send());
9499
} catch (ParseException pe) {
95100
// This error occurs if the Access Token Response is not well-formed,
@@ -132,6 +137,11 @@ public TokenResponseAttributes exchange(AuthorizationCodeAuthenticationToken aut
132137
.build();
133138
}
134139

140+
public final void setHttpClientConfig(HttpClientConfig httpClientConfig) {
141+
Assert.notNull(httpClientConfig, "httpClientConfig cannot be null");
142+
this.httpClientConfig = httpClientConfig;
143+
}
144+
135145
private URI toURI(String uriStr) {
136146
try {
137147
return new URI(uriStr);

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusOAuth2UserService.java

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.security.core.GrantedAuthority;
3232
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
3333
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
34+
import org.springframework.security.oauth2.core.http.HttpClientConfig;
3435
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3536
import org.springframework.security.oauth2.client.user.OAuth2UserService;
3637
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -72,6 +73,7 @@ public class NimbusOAuth2UserService implements OAuth2UserService {
7273
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
7374
private Map<URI, String> userNameAttributeNames = Collections.unmodifiableMap(Collections.emptyMap());
7475
private Map<URI, Class<? extends OAuth2User>> customUserTypes = Collections.unmodifiableMap(Collections.emptyMap());
76+
private HttpClientConfig httpClientConfig = new HttpClientConfig();
7577

7678
public NimbusOAuth2UserService() {
7779
}
@@ -151,6 +153,8 @@ protected Map<String, Object> getUserInfo(OAuth2AuthenticationToken token) throw
151153
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
152154
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
153155
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
156+
httpRequest.setConnectTimeout(this.httpClientConfig.getConnectTimeout());
157+
httpRequest.setReadTimeout(this.httpClientConfig.getReadTimeout());
154158
HTTPResponse httpResponse;
155159

156160
try {
@@ -215,6 +219,11 @@ public final void setCustomUserTypes(Map<URI, Class<? extends OAuth2User>> custo
215219
this.customUserTypes = Collections.unmodifiableMap(new HashMap<>(customUserTypes));
216220
}
217221

222+
public final void setHttpClientConfig(HttpClientConfig httpClientConfig) {
223+
Assert.notNull(httpClientConfig, "httpClientConfig cannot be null");
224+
this.httpClientConfig = httpClientConfig;
225+
}
226+
218227
private URI getUserInfoUri(OAuth2AuthenticationToken token) {
219228
ClientRegistration clientRegistration = token.getClientRegistration();
220229
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.oauth2.core.http;
17+
18+
import org.springframework.util.Assert;
19+
20+
/**
21+
* This class provides the capability for configuring the underlying HTTP client.
22+
*
23+
* <p>
24+
* To customize the configuration of the underlying HTTP client, create/configure
25+
* an instance of {@link HttpClientConfig} and register it with the <code>ApplicationContext</code>.
26+
*
27+
* <p>
28+
* For example:
29+
*
30+
* <pre>
31+
* &#064;Bean
32+
* public HttpClientConfig httpClientConfig() {
33+
* HttpClientConfig httpClientConfig = new HttpClientConfig();
34+
* httpClientConfig.setConnectTimeout(60000);
35+
* httpClientConfig.setReadTimeout(60000);
36+
* return httpClientConfig;
37+
* }
38+
* </pre>
39+
*
40+
* @author Joe Grandja
41+
* @since 5.0
42+
*/
43+
public class HttpClientConfig {
44+
public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
45+
public static final int DEFAULT_READ_TIMEOUT = 30000;
46+
private int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
47+
private int readTimeout = DEFAULT_READ_TIMEOUT;
48+
49+
/**
50+
* Returns the timeout in milliseconds until a connection is established.
51+
*
52+
* @return the connect timeout value in milliseconds
53+
*/
54+
public int getConnectTimeout() {
55+
return this.connectTimeout;
56+
}
57+
58+
/**
59+
* Sets the timeout in milliseconds until a connection is established.
60+
* A timeout value of 0 implies the option is disabled (timeout of infinity).
61+
*
62+
* @param connectTimeout the connect timeout value in milliseconds
63+
*/
64+
public void setConnectTimeout(int connectTimeout) {
65+
Assert.isTrue(connectTimeout >= 0, "connectTimeout cannot be negative");
66+
this.connectTimeout = connectTimeout;
67+
}
68+
69+
/**
70+
* Returns the timeout in milliseconds for inactivity when reading from the <code>InputStream</code>.
71+
*
72+
* @return the read timeout value in milliseconds
73+
*/
74+
public int getReadTimeout() {
75+
return this.readTimeout;
76+
}
77+
78+
/**
79+
* Sets the timeout in milliseconds for inactivity when reading from the <code>InputStream</code>.
80+
* A timeout value of 0 implies the option is disabled (timeout of infinity).
81+
*
82+
* @param readTimeout the read timeout value in milliseconds
83+
*/
84+
public void setReadTimeout(int readTimeout) {
85+
Assert.isTrue(readTimeout >= 0, "readTimeout cannot be negative");
86+
this.readTimeout = readTimeout;
87+
}
88+
89+
}

0 commit comments

Comments
 (0)