|
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; |
|
55 | 61 | * |
56 | 62 | * @author Thomas Vitale |
57 | 63 | * @author Rafiullah Hamedy |
| 64 | + * @author Evgeniy Cheban |
58 | 65 | * @since 5.2 |
59 | 66 | */ |
60 | 67 | final class JwtDecoderProviderConfigurationUtils { |
61 | 68 |
|
| 69 | + private static final Log LOG = LogFactory.getLog(JwtDecoderProviderConfigurationUtils.class); |
| 70 | + |
62 | 71 | private static final String OIDC_METADATA_PATH = "/.well-known/openid-configuration"; |
63 | 72 |
|
64 | 73 | private static final String OAUTH_METADATA_PATH = "/.well-known/oauth-authorization-server"; |
@@ -158,28 +167,26 @@ private static String getMetadataIssuer(Map<String, Object> configuration) { |
158 | 167 | } |
159 | 168 |
|
160 | 169 | 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 |
| 170 | + // @formatter:off |
| 171 | + RetryPolicy retryPolicy = RetryPolicy.builder() |
| 172 | + .excludes(IllegalArgumentException.class) |
| 173 | + .maxRetries(uris.length) |
| 174 | + .build(); |
| 175 | + // @formatter:on |
| 176 | + RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); |
| 177 | + try { |
| 178 | + return retryTemplate.execute(new RetryableConfiguration(rest, issuer, uris)); |
| 179 | + } |
| 180 | + catch (RetryException ex) { |
| 181 | + Throwable lastException = ex.getLastException(); |
| 182 | + LOG.error(""" |
| 183 | + Unable to resolve the Configuration with the provided Issuer of "%s", after: %d attempts |
| 184 | + """.formatted(issuer, ex.getRetryCount()), lastException); |
| 185 | + if (lastException instanceof RuntimeException) { |
| 186 | + throw (RuntimeException) lastException; |
180 | 187 | } |
| 188 | + throw new RuntimeException(lastException.getMessage(), lastException); |
181 | 189 | } |
182 | | - throw new IllegalArgumentException(errorMessage); |
183 | 190 | } |
184 | 191 |
|
185 | 192 | static UriComponents oidc(String issuer) { |
@@ -209,4 +216,55 @@ static UriComponents oauth(String issuer) { |
209 | 216 | // @formatter:on |
210 | 217 | } |
211 | 218 |
|
| 219 | + private static final class RetryableConfiguration implements Retryable<Map<String, Object>> { |
| 220 | + |
| 221 | + private int currentUriIndex = 0; |
| 222 | + |
| 223 | + private final RestOperations rest; |
| 224 | + |
| 225 | + private final String issuer; |
| 226 | + |
| 227 | + private final UriComponents[] uris; |
| 228 | + |
| 229 | + private RetryableConfiguration(RestOperations rest, String issuer, UriComponents[] uris) { |
| 230 | + this.rest = rest; |
| 231 | + this.issuer = issuer; |
| 232 | + this.uris = uris; |
| 233 | + } |
| 234 | + |
| 235 | + @Override |
| 236 | + public Map<String, Object> execute() { |
| 237 | + if (this.currentUriIndex < this.uris.length) { |
| 238 | + try { |
| 239 | + UriComponents uri = this.uris[this.currentUriIndex++]; |
| 240 | + RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build(); |
| 241 | + ResponseEntity<Map<String, Object>> response; |
| 242 | + response = this.rest.exchange(request, STRING_OBJECT_MAP); |
| 243 | + Map<String, Object> configuration = response.getBody(); |
| 244 | + Assert.notNull(configuration, "configuration must not be null"); |
| 245 | + Assert.isTrue(configuration.get("jwks_uri") != null, "The public JWK set URI must not be null"); |
| 246 | + return configuration; |
| 247 | + } |
| 248 | + catch (IllegalArgumentException ex) { |
| 249 | + throw ex; |
| 250 | + } |
| 251 | + catch (RuntimeException ex) { |
| 252 | + if (!(ex instanceof HttpClientErrorException err && err.getStatusCode().is4xxClientError())) { |
| 253 | + throw new IllegalArgumentException(getErrorMessage(), ex); |
| 254 | + } |
| 255 | + // else try another endpoint |
| 256 | + throw ex; |
| 257 | + } |
| 258 | + } |
| 259 | + throw new IllegalArgumentException(getErrorMessage()); |
| 260 | + } |
| 261 | + |
| 262 | + private String getErrorMessage() { |
| 263 | + return """ |
| 264 | + Unable to resolve the Configuration with the provided Issuer of "%s" |
| 265 | + """.formatted(this.issuer); |
| 266 | + } |
| 267 | + |
| 268 | + } |
| 269 | + |
212 | 270 | } |
0 commit comments