Skip to content

Commit b4e0873

Browse files
author
Steve Riesenberg
committed
Merge branch '6.0.x' into 6.1.x
Closes gh-14041
2 parents 8ca7d19 + bb732e9 commit b4e0873

File tree

2 files changed

+45
-13
lines changed

2 files changed

+45
-13
lines changed

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,28 +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 Mono<String> jwkSetURL;
53+
private final Mono<String> jwkSetUrlProvider;
4954

5055
ReactiveRemoteJWKSource(String jwkSetURL) {
5156
Assert.hasText(jwkSetURL, "jwkSetURL cannot be empty");
52-
this.jwkSetURL = Mono.just(jwkSetURL);
57+
this.jwkSetUrlProvider = Mono.just(jwkSetURL);
5358
}
5459

55-
ReactiveRemoteJWKSource(Mono<String> jwkSetURL) {
56-
Assert.notNull(jwkSetURL, "jwkSetURL cannot be null");
57-
this.jwkSetURL = jwkSetURL.cache();
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)));
5864
}
5965

6066
@Override
6167
public Mono<List<JWK>> get(JWKSelector jwkSelector) {
6268
// @formatter:off
6369
return this.cachedJWKSet.get()
64-
.switchIfEmpty(Mono.defer(() -> getJWKSet()))
70+
.switchIfEmpty(Mono.defer(this::getJWKSet))
6571
.flatMap((jwkSet) -> get(jwkSelector, jwkSet))
6672
.switchIfEmpty(Mono.defer(() -> getJWKSet()
67-
.map((jwkSet) -> jwkSelector.select(jwkSet)))
73+
.map(jwkSelector::select))
6874
);
6975
// @formatter:on
7076
}
@@ -100,13 +106,15 @@ private Mono<List<JWK>> get(JWKSelector jwkSelector, JWKSet jwkSet) {
100106
*/
101107
private Mono<JWKSet> getJWKSet() {
102108
// @formatter:off
103-
return this.jwkSetURL.flatMap((jwkSetURL) -> this.webClient.get()
104-
.uri(jwkSetURL)
105-
.retrieve()
106-
.bodyToMono(String.class))
109+
return this.jwkSetUrlProvider
110+
.flatMap((jwkSetURL) -> this.webClient.get()
111+
.uri(jwkSetURL)
112+
.retrieve()
113+
.bodyToMono(String.class)
114+
)
107115
.map(this::parse)
108116
.doOnNext((jwkSet) -> this.cachedJWKSet
109-
.set(Mono.just(jwkSet))
117+
.set(Mono.just(jwkSet))
110118
)
111119
.cache();
112120
// @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)