Skip to content

Commit bb732e9

Browse files
author
Steve Riesenberg
committed
Merge branch '5.8.x' into 6.0.x
Closes gh-14040
2 parents 9b2b7e5 + 5161712 commit bb732e9

File tree

2 files changed

+48
-11
lines changed

2 files changed

+48
-11
lines changed

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -43,23 +43,34 @@ class ReactiveRemoteJWKSource implements ReactiveJWKSource {
4343
*/
4444
private final AtomicReference<Mono<JWKSet>> cachedJWKSet = new AtomicReference<>(Mono.empty());
4545

46+
/**
47+
* The cached JWK set URL.
48+
*/
49+
private final AtomicReference<String> cachedJwkSetUrl = new AtomicReference<>();
50+
4651
private WebClient webClient = WebClient.create();
4752

48-
private final String jwkSetURL;
53+
private final Mono<String> jwkSetUrlProvider;
4954

5055
ReactiveRemoteJWKSource(String jwkSetURL) {
5156
Assert.hasText(jwkSetURL, "jwkSetURL cannot be empty");
52-
this.jwkSetURL = jwkSetURL;
57+
this.jwkSetUrlProvider = Mono.just(jwkSetURL);
58+
}
59+
60+
ReactiveRemoteJWKSource(Mono<String> jwkSetUrlProvider) {
61+
Assert.notNull(jwkSetUrlProvider, "jwkSetUrlProvider cannot be null");
62+
this.jwkSetUrlProvider = Mono.fromCallable(this.cachedJwkSetUrl::get)
63+
.switchIfEmpty(Mono.defer(() -> jwkSetUrlProvider.doOnNext(this.cachedJwkSetUrl::set)));
5364
}
5465

5566
@Override
5667
public Mono<List<JWK>> get(JWKSelector jwkSelector) {
5768
// @formatter:off
5869
return this.cachedJWKSet.get()
59-
.switchIfEmpty(Mono.defer(() -> getJWKSet()))
70+
.switchIfEmpty(Mono.defer(this::getJWKSet))
6071
.flatMap((jwkSet) -> get(jwkSelector, jwkSet))
6172
.switchIfEmpty(Mono.defer(() -> getJWKSet()
62-
.map((jwkSet) -> jwkSelector.select(jwkSet)))
73+
.map(jwkSelector::select))
6374
);
6475
// @formatter:on
6576
}
@@ -95,13 +106,15 @@ private Mono<List<JWK>> get(JWKSelector jwkSelector, JWKSet jwkSet) {
95106
*/
96107
private Mono<JWKSet> getJWKSet() {
97108
// @formatter:off
98-
return this.webClient.get()
99-
.uri(this.jwkSetURL)
100-
.retrieve()
101-
.bodyToMono(String.class)
109+
return this.jwkSetUrlProvider
110+
.flatMap((jwkSetURL) -> this.webClient.get()
111+
.uri(jwkSetURL)
112+
.retrieve()
113+
.bodyToMono(String.class)
114+
)
102115
.map(this::parse)
103116
.doOnNext((jwkSet) -> this.cachedJWKSet
104-
.set(Mono.just(jwkSet))
117+
.set(Mono.just(jwkSet))
105118
)
106119
.cache();
107120
// @formatter:on

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/ReactiveRemoteJWKSourceTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -18,6 +18,7 @@
1818

1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.function.Supplier;
2122

2223
import com.nimbusds.jose.jwk.JWK;
2324
import com.nimbusds.jose.jwk.JWKMatcher;
@@ -31,10 +32,16 @@
3132
import org.junit.jupiter.api.extension.ExtendWith;
3233
import org.mockito.Mock;
3334
import org.mockito.junit.jupiter.MockitoExtension;
35+
import reactor.core.publisher.Mono;
36+
37+
import org.springframework.web.reactive.function.client.WebClientResponseException;
3438

3539
import static org.assertj.core.api.Assertions.assertThat;
40+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3641
import static org.mockito.ArgumentMatchers.any;
3742
import static org.mockito.BDDMockito.given;
43+
import static org.mockito.BDDMockito.willReturn;
44+
import static org.mockito.BDDMockito.willThrow;
3845

3946
/**
4047
* @author Rob Winch
@@ -52,6 +59,9 @@ public class ReactiveRemoteJWKSourceTests {
5259

5360
private MockWebServer server;
5461

62+
@Mock
63+
private Supplier<String> mockStringSupplier;
64+
5565
// @formatter:off
5666
private String keys = "{\n"
5767
+ " \"keys\": [\n"
@@ -156,4 +166,18 @@ public void getWhenNoMatchAndKeyIdMatchThenEmpty() {
156166
assertThat(this.source.get(this.selector).block()).isEmpty();
157167
}
158168

169+
@Test
170+
public void getShouldRecoverAndReturnKeysAfterErrorCase() {
171+
given(this.matcher.matches(any())).willReturn(true);
172+
this.source = new ReactiveRemoteJWKSource(Mono.fromSupplier(this.mockStringSupplier));
173+
willThrow(WebClientResponseException.ServiceUnavailable.class).given(this.mockStringSupplier).get();
174+
// first case: id provider has error state
175+
assertThatExceptionOfType(WebClientResponseException.ServiceUnavailable.class)
176+
.isThrownBy(() -> this.source.get(this.selector).block());
177+
// second case: id provider is healthy again
178+
willReturn(this.server.url("/").toString()).given(this.mockStringSupplier).get();
179+
List<JWK> actual = this.source.get(this.selector).block();
180+
assertThat(actual).isNotEmpty();
181+
}
182+
159183
}

0 commit comments

Comments
 (0)