Skip to content

Commit e1a21c5

Browse files
Remove Nimbus(Reactive)OpaqueTokenIntrospector
Signed-off-by: Tran Ngoc Nhan <[email protected]>
1 parent 08cbdb4 commit e1a21c5

File tree

8 files changed

+121
-1302
lines changed

8 files changed

+121
-1302
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

Lines changed: 61 additions & 36 deletions
Large diffs are not rendered by default.

config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.nimbusds.jose.crypto.RSASSASigner;
3737
import com.nimbusds.jose.jwk.JWKSet;
3838
import com.nimbusds.jose.jwk.RSAKey;
39+
import com.nimbusds.jose.util.JSONObjectUtils;
3940
import jakarta.servlet.http.HttpServletRequest;
4041
import net.minidev.json.JSONObject;
4142
import okhttp3.mockwebserver.MockResponse;
@@ -57,6 +58,7 @@
5758
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
5859
import org.springframework.beans.factory.xml.ParserContext;
5960
import org.springframework.beans.factory.xml.XmlReaderContext;
61+
import org.springframework.core.ParameterizedTypeReference;
6062
import org.springframework.core.convert.converter.Converter;
6163
import org.springframework.core.io.ClassPathResource;
6264
import org.springframework.http.HttpHeaders;
@@ -84,9 +86,9 @@
8486
import org.springframework.security.oauth2.jwt.TestJwts;
8587
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
8688
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
87-
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
8889
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
8990
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
91+
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
9092
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
9193
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
9294
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -139,7 +141,7 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
139141
@Test
140142
public void getWhenValidBearerTokenThenAcceptsRequest() throws Exception {
141143
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
142-
mockRestOperations(jwks("Default"));
144+
mockJwksRestOperations(jwks("Default"));
143145
String token = this.token("ValidNoScopes");
144146
// @formatter:off
145147
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -150,7 +152,7 @@ public void getWhenValidBearerTokenThenAcceptsRequest() throws Exception {
150152
@Test
151153
public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
152154
this.spring.configLocations(xml("JwtRestOperations"), xml("JwtCustomSecurityContextHolderStrategy")).autowire();
153-
mockRestOperations(jwks("Default"));
155+
mockJwksRestOperations(jwks("Default"));
154156
String token = this.token("ValidNoScopes");
155157
// @formatter:off
156158
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -175,7 +177,7 @@ public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception {
175177
@Test
176178
public void getWhenExpiredBearerTokenThenInvalidToken() throws Exception {
177179
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
178-
mockRestOperations(jwks("Default"));
180+
mockJwksRestOperations(jwks("Default"));
179181
String token = this.token("Expired");
180182
// @formatter:off
181183
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -187,7 +189,7 @@ public void getWhenExpiredBearerTokenThenInvalidToken() throws Exception {
187189
@Test
188190
public void getWhenBadJwkEndpointThen500() throws Exception {
189191
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
190-
mockRestOperations("malformed");
192+
mockJwksRestOperations("malformed");
191193
String token = this.token("ValidNoScopes");
192194
// @formatter:off
193195
assertThatExceptionOfType(AuthenticationServiceException.class)
@@ -219,7 +221,7 @@ public void getWhenMalformedBearerTokenThenInvalidToken() throws Exception {
219221
@Test
220222
public void getWhenMalformedPayloadThenInvalidToken() throws Exception {
221223
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
222-
mockRestOperations(jwks("Default"));
224+
mockJwksRestOperations(jwks("Default"));
223225
String token = this.token("MalformedPayload");
224226
// @formatter:off
225227
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -242,7 +244,7 @@ public void getWhenUnsignedBearerTokenThenInvalidToken() throws Exception {
242244
@Test
243245
public void getWhenBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception {
244246
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
245-
this.mockRestOperations(jwks("Default"));
247+
this.mockJwksRestOperations(jwks("Default"));
246248
String token = this.token("TooEarly");
247249
// @formatter:off
248250
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -299,7 +301,7 @@ public void getWhenNoBearerTokenThenUnauthorized() throws Exception {
299301
@Test
300302
public void getWhenSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception {
301303
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
302-
mockRestOperations(jwks("Default"));
304+
mockJwksRestOperations(jwks("Default"));
303305
String token = this.token("ValidMessageReadScope");
304306
// @formatter:off
305307
this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token))
@@ -310,7 +312,7 @@ public void getWhenSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exce
310312
@Test
311313
public void getWhenInsufficientScopeThenInsufficientScopeError() throws Exception {
312314
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
313-
mockRestOperations(jwks("Default"));
315+
mockJwksRestOperations(jwks("Default"));
314316
String token = this.token("ValidNoScopes");
315317
// @formatter:off
316318
this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token))
@@ -322,7 +324,7 @@ public void getWhenInsufficientScopeThenInsufficientScopeError() throws Exceptio
322324
@Test
323325
public void getWhenInsufficientScpThenInsufficientScopeError() throws Exception {
324326
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
325-
mockRestOperations(jwks("Default"));
327+
mockJwksRestOperations(jwks("Default"));
326328
String token = this.token("ValidMessageWriteScp");
327329
// @formatter:off
328330
this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token))
@@ -334,7 +336,7 @@ public void getWhenInsufficientScpThenInsufficientScopeError() throws Exception
334336
@Test
335337
public void getWhenAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception {
336338
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
337-
mockRestOperations(jwks("Empty"));
339+
mockJwksRestOperations(jwks("Empty"));
338340
String token = this.token("ValidNoScopes");
339341
// @formatter:off
340342
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -346,7 +348,7 @@ public void getWhenAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws
346348
@Test
347349
public void getWhenAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception {
348350
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
349-
mockRestOperations(jwks("TwoKeys"));
351+
mockJwksRestOperations(jwks("TwoKeys"));
350352
String token = this.token("ValidNoScopes");
351353
// @formatter:off
352354
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token))
@@ -357,7 +359,7 @@ public void getWhenAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exc
357359
@Test
358360
public void getWhenKeyMatchesByKidThenOk() throws Exception {
359361
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
360-
mockRestOperations(jwks("TwoKeys"));
362+
mockJwksRestOperations(jwks("TwoKeys"));
361363
String token = this.token("Kid");
362364
// @formatter:off
363365
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token))
@@ -368,7 +370,7 @@ public void getWhenKeyMatchesByKidThenOk() throws Exception {
368370
@Test
369371
public void postWhenValidBearerTokenAndNoCsrfTokenThenOk() throws Exception {
370372
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
371-
mockRestOperations(jwks("Default"));
373+
mockJwksRestOperations(jwks("Default"));
372374
String token = this.token("ValidNoScopes");
373375
// @formatter:off
374376
this.mvc.perform(post("/authenticated").header("Authorization", "Bearer " + token))
@@ -390,7 +392,7 @@ public void postWhenNoBearerTokenThenCsrfDenies() throws Exception {
390392
@Test
391393
public void postWhenExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception {
392394
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
393-
mockRestOperations(jwks("Default"));
395+
mockJwksRestOperations(jwks("Default"));
394396
String token = this.token("Expired");
395397
// @formatter:off
396398
this.mvc.perform(post("/authenticated").header("Authorization", "Bearer " + token))
@@ -402,7 +404,7 @@ public void postWhenExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Excepti
402404
@Test
403405
public void requestWhenJwtThenSessionIsNotCreated() throws Exception {
404406
this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire();
405-
mockRestOperations(jwks("Default"));
407+
mockJwksRestOperations(jwks("Default"));
406408
String token = this.token("ValidNoScopes");
407409
// @formatter:off
408410
MvcResult result = this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -438,7 +440,7 @@ public void requestWhenNoBearerTokenThenSessionIsCreated() throws Exception {
438440
@Test
439441
public void requestWhenSessionManagementConfiguredThenUses() throws Exception {
440442
this.spring.configLocations(xml("JwtRestOperations"), xml("AlwaysSessionCreation")).autowire();
441-
mockRestOperations(jwks("Default"));
443+
mockJwksRestOperations(jwks("Default"));
442444
String token = this.token("ValidNoScopes");
443445
// @formatter:off
444446
MvcResult result = this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -587,7 +589,7 @@ public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Except
587589
@Test
588590
public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception {
589591
this.spring.configLocations(xml("MockJwtValidator"), xml("Jwt")).autowire();
590-
mockRestOperations(jwks("Default"));
592+
mockJwksRestOperations(jwks("Default"));
591593
String token = this.token("ValidNoScopes");
592594
OAuth2TokenValidator<Jwt> jwtValidator = this.spring.getContext().getBean(OAuth2TokenValidator.class);
593595
OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri");
@@ -602,7 +604,7 @@ public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() th
602604
@Test
603605
public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception {
604606
this.spring.configLocations(xml("UnexpiredJwtClockSkew"), xml("Jwt")).autowire();
605-
mockRestOperations(jwks("Default"));
607+
mockJwksRestOperations(jwks("Default"));
606608
String token = this.token("ExpiresAt4687177990");
607609
// @formatter:off
608610
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -613,7 +615,7 @@ public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throw
613615
@Test
614616
public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception {
615617
this.spring.configLocations(xml("ExpiredJwtClockSkew"), xml("Jwt")).autowire();
616-
mockRestOperations(jwks("Default"));
618+
mockJwksRestOperations(jwks("Default"));
617619
String token = this.token("ExpiresAt4687177990");
618620
// @formatter:off
619621
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
@@ -675,7 +677,7 @@ public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToke
675677
@Test
676678
public void getWhenIntrospectingThenOk() throws Exception {
677679
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire();
678-
mockRestOperations(json("Active"));
680+
mockJsonRestOperations(json("Active"));
679681
// @formatter:off
680682
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token"))
681683
.andExpect(status().isNotFound());
@@ -686,7 +688,7 @@ public void getWhenIntrospectingThenOk() throws Exception {
686688
public void configureWhenIntrospectingWithAuthenticationConverterThenUses() throws Exception {
687689
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueTokenAndAuthenticationConverter"))
688690
.autowire();
689-
mockRestOperations(json("Active"));
691+
mockJsonRestOperations(json("Active"));
690692
OpaqueTokenAuthenticationConverter converter = bean(OpaqueTokenAuthenticationConverter.class);
691693
given(converter.convert(any(), any())).willReturn(new TestingAuthenticationToken("user", "pass", "app"));
692694
// @formatter:off
@@ -699,7 +701,7 @@ public void configureWhenIntrospectingWithAuthenticationConverterThenUses() thro
699701
@Test
700702
public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
701703
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire();
702-
mockRestOperations(json("Inactive"));
704+
mockJsonRestOperations(json("Inactive"));
703705
// @formatter:off
704706
MockHttpServletRequestBuilder request = get("/")
705707
.header("Authorization", "Bearer token");
@@ -712,7 +714,7 @@ public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
712714
@Test
713715
public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception {
714716
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire();
715-
mockRestOperations(json("ActiveNoScopes"));
717+
mockJsonRestOperations(json("ActiveNoScopes"));
716718
// @formatter:off
717719
this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer token"))
718720
.andExpect(status().isForbidden())
@@ -818,7 +820,7 @@ public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedBy
818820
@Test
819821
public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception {
820822
this.spring.configLocations(xml("JwtRestOperations"), xml("BasicAndResourceServer")).autowire();
821-
mockRestOperations(jwks("Default"));
823+
mockJwksRestOperations(jwks("Default"));
822824
String token = this.token("ValidNoScopes");
823825
// @formatter:off
824826
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token))
@@ -963,14 +965,29 @@ private void mockWebServer(String response) {
963965
.setBody(response));
964966
}
965967

966-
private void mockRestOperations(String response) {
968+
private void mockJwksRestOperations(String response) {
967969
RestOperations rest = this.spring.getContext().getBean(RestOperations.class);
968970
HttpHeaders headers = new HttpHeaders();
969971
headers.setContentType(MediaType.APPLICATION_JSON);
970972
ResponseEntity<String> entity = new ResponseEntity<>(response, headers, HttpStatus.OK);
971973
given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity);
972974
}
973975

976+
private void mockJsonRestOperations(String response) {
977+
try {
978+
RestOperations rest = this.spring.getContext().getBean(RestOperations.class);
979+
HttpHeaders headers = new HttpHeaders();
980+
headers.setContentType(MediaType.APPLICATION_JSON);
981+
ResponseEntity<Map<String, Object>> entity = new ResponseEntity<>(JSONObjectUtils.parse(response), headers,
982+
HttpStatus.OK);
983+
given(rest.exchange(any(RequestEntity.class), eq(new ParameterizedTypeReference<Map<String, Object>>() {
984+
}))).willReturn(entity);
985+
}
986+
catch (Exception ex) {
987+
throw new IllegalArgumentException(ex);
988+
}
989+
}
990+
974991
private String json(String name) throws IOException {
975992
return resource(name + ".json");
976993
}
@@ -1047,7 +1064,7 @@ static class OpaqueTokenIntrospectorFactoryBean implements FactoryBean<OpaqueTok
10471064

10481065
@Override
10491066
public OpaqueTokenIntrospector getObject() throws Exception {
1050-
return new NimbusOpaqueTokenIntrospector("https://idp.example.org", this.rest);
1067+
return new SpringOpaqueTokenIntrospector("https://idp.example.org", this.rest);
10511068
}
10521069

10531070
@Override

config/src/test/kotlin/org/springframework/security/config/annotation/web/oauth2/resourceserver/OpaqueTokenDslTests.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith
2424
import org.springframework.beans.factory.annotation.Autowired
2525
import org.springframework.context.annotation.Bean
2626
import org.springframework.context.annotation.Configuration
27+
import org.springframework.core.ParameterizedTypeReference
2728
import org.springframework.http.HttpHeaders
2829
import org.springframework.http.HttpStatus
2930
import org.springframework.http.MediaType
@@ -41,7 +42,6 @@ import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrinci
4142
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens
4243
import org.springframework.security.oauth2.jwt.JwtClaimNames
4344
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication
44-
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector
4545
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
4646
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector
4747
import org.springframework.security.web.SecurityFilterChain
@@ -84,15 +84,15 @@ class OpaqueTokenDslTests {
8484
val headers = HttpHeaders().apply {
8585
contentType = MediaType.APPLICATION_JSON
8686
}
87-
val entity = ResponseEntity("{\n" +
88-
" \"active\" : true,\n" +
89-
" \"sub\": \"test-subject\",\n" +
90-
" \"scope\": \"message:read\",\n" +
91-
" \"exp\": 4683883211\n" +
92-
"}", headers, HttpStatus.OK)
87+
val responseBody: Map<String, Any> = mapOf(
88+
"active" to true,
89+
"sub" to "test-subject",
90+
"scope" to "message:read",
91+
"exp" to 4683883211
92+
)
9393
every {
94-
DefaultOpaqueConfig.REST.exchange(any(), eq(String::class.java))
95-
} returns entity
94+
DefaultOpaqueConfig.REST.exchange(any(), any<ParameterizedTypeReference<Map<String, Any>>>())
95+
} returns ResponseEntity(responseBody, headers, HttpStatus.OK)
9696

9797
this.mockMvc.get("/authenticated") {
9898
header("Authorization", "Bearer token")
@@ -127,8 +127,8 @@ class OpaqueTokenDslTests {
127127
open fun rest(): RestOperations = REST
128128

129129
@Bean
130-
open fun tokenIntrospectionClient(): NimbusOpaqueTokenIntrospector {
131-
return NimbusOpaqueTokenIntrospector("https://example.org/introspect", REST)
130+
open fun tokenIntrospectionClient(): OpaqueTokenIntrospector {
131+
return SpringOpaqueTokenIntrospector("https://example.org/introspect", REST)
132132
}
133133
}
134134

0 commit comments

Comments
 (0)