|
33 | 33 | import com.nimbusds.jose.proc.JWSVerificationKeySelector; |
34 | 34 | import com.nimbusds.jose.proc.SecurityContext; |
35 | 35 | import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; |
| 36 | +import org.apache.commons.logging.Log; |
| 37 | +import org.apache.commons.logging.LogFactory; |
36 | 38 |
|
37 | 39 | import org.springframework.core.ParameterizedTypeReference; |
| 40 | +import org.springframework.core.retry.RetryException; |
| 41 | +import org.springframework.core.retry.RetryPolicy; |
| 42 | +import org.springframework.core.retry.RetryTemplate; |
| 43 | +import org.springframework.core.retry.Retryable; |
38 | 44 | import org.springframework.http.RequestEntity; |
39 | 45 | import org.springframework.http.ResponseEntity; |
40 | 46 | import org.springframework.http.client.SimpleClientHttpRequestFactory; |
41 | 47 | import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; |
42 | 48 | import org.springframework.util.Assert; |
43 | | -import org.springframework.web.client.HttpClientErrorException; |
44 | 49 | import org.springframework.web.client.RestOperations; |
45 | 50 | import org.springframework.web.client.RestTemplate; |
46 | 51 | import org.springframework.web.util.UriComponents; |
|
55 | 60 | * |
56 | 61 | * @author Thomas Vitale |
57 | 62 | * @author Rafiullah Hamedy |
| 63 | + * @author Evgeniy Cheban |
58 | 64 | * @since 5.2 |
59 | 65 | */ |
60 | 66 | final class JwtDecoderProviderConfigurationUtils { |
61 | 67 |
|
| 68 | + private static final Log LOG = LogFactory.getLog(JwtDecoderProviderConfigurationUtils.class); |
| 69 | + |
62 | 70 | private static final String OIDC_METADATA_PATH = "/.well-known/openid-configuration"; |
63 | 71 |
|
64 | 72 | private static final String OAUTH_METADATA_PATH = "/.well-known/oauth-authorization-server"; |
@@ -158,28 +166,26 @@ private static String getMetadataIssuer(Map<String, Object> configuration) { |
158 | 166 | } |
159 | 167 |
|
160 | 168 | private static Map<String, Object> getConfiguration(String issuer, RestOperations rest, UriComponents... uris) { |
161 | | - String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " + "\"" + issuer + "\""; |
162 | | - for (UriComponents uri : uris) { |
163 | | - try { |
164 | | - RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build(); |
165 | | - ResponseEntity<Map<String, Object>> response = rest.exchange(request, STRING_OBJECT_MAP); |
166 | | - Map<String, Object> configuration = response.getBody(); |
167 | | - Assert.notNull(configuration, "configuration must not be null"); |
168 | | - Assert.isTrue(configuration.get("jwks_uri") != null, "The public JWK set URI must not be null"); |
169 | | - return configuration; |
170 | | - } |
171 | | - catch (IllegalArgumentException ex) { |
172 | | - throw ex; |
173 | | - } |
174 | | - catch (RuntimeException ex) { |
175 | | - if (!(ex instanceof HttpClientErrorException |
176 | | - && ((HttpClientErrorException) ex).getStatusCode().is4xxClientError())) { |
177 | | - throw new IllegalArgumentException(errorMessage, ex); |
178 | | - } |
179 | | - // else try another endpoint |
| 169 | + // @formatter:off |
| 170 | + RetryPolicy retryPolicy = RetryPolicy.builder() |
| 171 | + .excludes(IllegalArgumentException.class) |
| 172 | + .maxRetries(uris.length) |
| 173 | + .build(); |
| 174 | + // @formatter:on |
| 175 | + RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); |
| 176 | + try { |
| 177 | + return retryTemplate.execute(new RetryableConfiguration(rest, issuer, uris)); |
| 178 | + } |
| 179 | + catch (RetryException ex) { |
| 180 | + Throwable lastException = ex.getLastException(); |
| 181 | + LOG.error(""" |
| 182 | + Unable to resolve the Configuration with the provided Issuer of "%s", after: %d attempts |
| 183 | + """.formatted(issuer, ex.getRetryCount()), lastException); |
| 184 | + if (lastException instanceof RuntimeException) { |
| 185 | + throw (RuntimeException) lastException; |
180 | 186 | } |
| 187 | + throw new RuntimeException(lastException.getMessage(), lastException); |
181 | 188 | } |
182 | | - throw new IllegalArgumentException(errorMessage); |
183 | 189 | } |
184 | 190 |
|
185 | 191 | static UriComponents oidc(String issuer) { |
@@ -209,4 +215,38 @@ static UriComponents oauth(String issuer) { |
209 | 215 | // @formatter:on |
210 | 216 | } |
211 | 217 |
|
| 218 | + private static final class RetryableConfiguration implements Retryable<Map<String, Object>> { |
| 219 | + |
| 220 | + private int currentUriIndex = 0; |
| 221 | + |
| 222 | + private final RestOperations rest; |
| 223 | + |
| 224 | + private final String issuer; |
| 225 | + |
| 226 | + private final UriComponents[] uris; |
| 227 | + |
| 228 | + private RetryableConfiguration(RestOperations rest, String issuer, UriComponents[] uris) { |
| 229 | + this.rest = rest; |
| 230 | + this.issuer = issuer; |
| 231 | + this.uris = uris; |
| 232 | + } |
| 233 | + |
| 234 | + @Override |
| 235 | + public Map<String, Object> execute() { |
| 236 | + if (this.currentUriIndex < this.uris.length) { |
| 237 | + UriComponents uri = this.uris[this.currentUriIndex++]; |
| 238 | + RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build(); |
| 239 | + ResponseEntity<Map<String, Object>> response = this.rest.exchange(request, STRING_OBJECT_MAP); |
| 240 | + Map<String, Object> configuration = response.getBody(); |
| 241 | + Assert.notNull(configuration, "configuration must not be null"); |
| 242 | + Assert.isTrue(configuration.get("jwks_uri") != null, "The public JWK set URI must not be null"); |
| 243 | + return configuration; |
| 244 | + } |
| 245 | + throw new IllegalArgumentException(""" |
| 246 | + Unable to resolve the Configuration with the provided Issuer of "%s" |
| 247 | + """.formatted(this.issuer)); |
| 248 | + } |
| 249 | + |
| 250 | + } |
| 251 | + |
212 | 252 | } |
0 commit comments