Skip to content

Commit 1ed9e3a

Browse files
committed
Resource Server AuthenticationManager
Making the authentication manager for jwt() and opaqueToken() configurable. Fixes: gh-6832 Fixes: gh-6849
1 parent 7200fa2 commit 1ed9e3a

File tree

2 files changed

+181
-31
lines changed

2 files changed

+181
-31
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.security.authentication.AbstractAuthenticationToken;
2525
import org.springframework.security.authentication.AuthenticationManager;
2626
import org.springframework.security.authentication.AuthenticationManagerResolver;
27+
import org.springframework.security.authentication.AuthenticationProvider;
2728
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2829
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
2930
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
@@ -199,18 +200,6 @@ public void configure(H http) throws Exception {
199200
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
200201
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
201202

202-
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
203-
if (resolver == null) {
204-
resolver = request -> http.getSharedObject(AuthenticationManager.class);
205-
}
206-
207-
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
208-
filter.setBearerTokenResolver(bearerTokenResolver);
209-
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
210-
filter = postProcess(filter);
211-
212-
http.addFilter(filter);
213-
214203
if (this.jwtConfigurer != null && this.opaqueTokenConfigurer != null) {
215204
throw new IllegalStateException("Spring Security only supports JWTs or Opaque Tokens, not both at the " +
216205
"same time");
@@ -225,30 +214,24 @@ public void configure(H http) throws Exception {
225214
"http.oauth2ResourceServer().opaque().");
226215
}
227216

228-
if (this.jwtConfigurer != null) {
229-
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
230-
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
231-
this.jwtConfigurer.getJwtAuthenticationConverter();
232-
233-
JwtAuthenticationProvider provider =
234-
new JwtAuthenticationProvider(decoder);
235-
provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
236-
provider = postProcess(provider);
237-
238-
http.authenticationProvider(provider);
217+
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
218+
if (resolver == null) {
219+
AuthenticationManager authenticationManager = getAuthenticationManager(http);
220+
resolver = request -> authenticationManager;
239221
}
240222

241-
if (this.opaqueTokenConfigurer != null) {
242-
OAuth2TokenIntrospectionClient introspectionClient = this.opaqueTokenConfigurer.getIntrospectionClient();
243-
OAuth2IntrospectionAuthenticationProvider provider =
244-
new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
245-
http.authenticationProvider(provider);
246-
}
223+
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
224+
filter.setBearerTokenResolver(bearerTokenResolver);
225+
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
226+
filter = postProcess(filter);
227+
228+
http.addFilter(filter);
247229
}
248230

249231
public class JwtConfigurer {
250232
private final ApplicationContext context;
251233

234+
private AuthenticationManager authenticationManager;
252235
private JwtDecoder decoder;
253236

254237
private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
@@ -258,6 +241,12 @@ public class JwtConfigurer {
258241
this.context = context;
259242
}
260243

244+
public JwtConfigurer authenticationManager(AuthenticationManager authenticationManager) {
245+
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
246+
this.authenticationManager = authenticationManager;
247+
return this;
248+
}
249+
261250
public JwtConfigurer decoder(JwtDecoder decoder) {
262251
this.decoder = decoder;
263252
return this;
@@ -290,11 +279,31 @@ JwtDecoder getJwtDecoder() {
290279

291280
return this.decoder;
292281
}
282+
283+
AuthenticationManager getAuthenticationManager(H http) {
284+
if (this.authenticationManager != null) {
285+
return this.authenticationManager;
286+
}
287+
288+
JwtDecoder decoder = getJwtDecoder();
289+
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
290+
getJwtAuthenticationConverter();
291+
292+
JwtAuthenticationProvider provider =
293+
new JwtAuthenticationProvider(decoder);
294+
provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
295+
AuthenticationProvider authenticationProvider = postProcess(provider);
296+
297+
http.authenticationProvider(authenticationProvider);
298+
299+
return http.getSharedObject(AuthenticationManager.class);
300+
}
293301
}
294302

295303
public class OpaqueTokenConfigurer {
296304
private final ApplicationContext context;
297305

306+
private AuthenticationManager authenticationManager;
298307
private String introspectionUri;
299308
private String clientId;
300309
private String clientSecret;
@@ -304,6 +313,12 @@ public class OpaqueTokenConfigurer {
304313
this.context = context;
305314
}
306315

316+
public OpaqueTokenConfigurer authenticationManager(AuthenticationManager authenticationManager) {
317+
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
318+
this.authenticationManager = authenticationManager;
319+
return this;
320+
}
321+
307322
public OpaqueTokenConfigurer introspectionUri(String introspectionUri) {
308323
Assert.notNull(introspectionUri, "introspectionUri cannot be null");
309324
this.introspectionUri = introspectionUri;
@@ -334,6 +349,19 @@ OAuth2TokenIntrospectionClient getIntrospectionClient() {
334349
}
335350
return this.context.getBean(OAuth2TokenIntrospectionClient.class);
336351
}
352+
353+
AuthenticationManager getAuthenticationManager(H http) {
354+
if (this.authenticationManager != null) {
355+
return this.authenticationManager;
356+
}
357+
358+
OAuth2TokenIntrospectionClient introspectionClient = getIntrospectionClient();
359+
OAuth2IntrospectionAuthenticationProvider provider =
360+
new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
361+
http.authenticationProvider(provider);
362+
363+
return http.getSharedObject(AuthenticationManager.class);
364+
}
337365
}
338366

339367
private void registerDefaultAccessDeniedHandler(H http) {
@@ -370,6 +398,18 @@ private void registerDefaultCsrfOverride(H http) {
370398
csrf.ignoringRequestMatchers(this.requestMatcher);
371399
}
372400

401+
AuthenticationManager getAuthenticationManager(H http) {
402+
if (this.jwtConfigurer != null) {
403+
return this.jwtConfigurer.getAuthenticationManager(http);
404+
}
405+
406+
if (this.opaqueTokenConfigurer != null) {
407+
return this.opaqueTokenConfigurer.getAuthenticationManager(http);
408+
}
409+
410+
return http.getSharedObject(AuthenticationManager.class);
411+
}
412+
373413
BearerTokenResolver getBearerTokenResolver() {
374414
if ( this.bearerTokenResolver == null ) {
375415
if ( this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0 ) {

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

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@
6565
import org.springframework.mock.web.MockHttpServletRequest;
6666
import org.springframework.security.access.prepost.PreAuthorize;
6767
import org.springframework.security.authentication.AbstractAuthenticationToken;
68+
import org.springframework.security.authentication.AuthenticationManager;
69+
import org.springframework.security.authentication.AuthenticationProvider;
6870
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
71+
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
6972
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7073
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
7174
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -79,8 +82,6 @@
7982
import org.springframework.security.oauth2.core.OAuth2Error;
8083
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
8184
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
82-
import org.springframework.security.oauth2.server.resource.introspection.NimbusOAuth2TokenIntrospectionClient;
83-
import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient;
8485
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
8586
import org.springframework.security.oauth2.jwt.Jwt;
8687
import org.springframework.security.oauth2.jwt.JwtClaimNames;
@@ -90,6 +91,9 @@
9091
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
9192
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
9293
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
94+
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationToken;
95+
import org.springframework.security.oauth2.server.resource.introspection.NimbusOAuth2TokenIntrospectionClient;
96+
import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient;
9397
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
9498
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
9599
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
@@ -120,8 +124,10 @@
120124
import static org.mockito.ArgumentMatchers.anyString;
121125
import static org.mockito.ArgumentMatchers.eq;
122126
import static org.mockito.Mockito.mock;
127+
import static org.mockito.Mockito.never;
123128
import static org.mockito.Mockito.verify;
124129
import static org.mockito.Mockito.when;
130+
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
125131
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
126132
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey;
127133
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -153,6 +159,8 @@ public class OAuth2ResourceServerConfigurerTests {
153159
private static final String INTROSPECTION_URI = "https://idp.example.com";
154160
private static final String CLIENT_ID = "client-id";
155161
private static final String CLIENT_SECRET = "client-secret";
162+
private static final OAuth2IntrospectionAuthenticationToken INTROSPECTION_AUTHENTICATION_TOKEN =
163+
new OAuth2IntrospectionAuthenticationToken(noScopes(), JWT_CLAIMS, Collections.emptyList());
156164

157165
@Autowired(required = false)
158166
MockMvc mvc;
@@ -1015,6 +1023,20 @@ public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToke
10151023
.andExpect(invalidTokenHeader("algorithm"));
10161024
}
10171025

1026+
@Test
1027+
public void getWhenCustomJwtAuthenticationManagerThenUsed() throws Exception {
1028+
this.spring.register(JwtAuthenticationManagerConfig.class, BasicController.class).autowire();
1029+
1030+
when(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
1031+
.thenReturn(JWT_AUTHENTICATION_TOKEN);
1032+
this.mvc.perform(get("/authenticated")
1033+
.with(bearerToken("token")))
1034+
.andExpect(status().isOk())
1035+
.andExpect(content().string("mock-test-subject"));
1036+
1037+
verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
1038+
}
1039+
10181040
// -- opaque
10191041

10201042

@@ -1052,6 +1074,20 @@ public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception {
10521074
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope")));
10531075
}
10541076

1077+
@Test
1078+
public void getWhenCustomIntrospectionAuthenticationManagerThenUsed() throws Exception {
1079+
this.spring.register(OpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire();
1080+
1081+
when(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
1082+
.thenReturn(INTROSPECTION_AUTHENTICATION_TOKEN);
1083+
this.mvc.perform(get("/authenticated")
1084+
.with(bearerToken("token")))
1085+
.andExpect(status().isOk())
1086+
.andExpect(content().string("mock-test-subject"));
1087+
1088+
verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
1089+
}
1090+
10551091
@Test
10561092
public void configureWhenOnlyIntrospectionUrlThenException() throws Exception {
10571093
assertThatCode(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire())
@@ -1191,6 +1227,30 @@ public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages()
11911227
.andExpect(content().string("basic-user"));
11921228
}
11931229

1230+
// -- authentication manager
1231+
1232+
@Test
1233+
public void getAuthenticationManagerWhenConfiguredAuthenticationManagerThenTakesPrecedence() {
1234+
ApplicationContext context = mock(ApplicationContext.class);
1235+
HttpSecurityBuilder http = mock(HttpSecurityBuilder.class);
1236+
1237+
OAuth2ResourceServerConfigurer oauth2ResourceServer = new OAuth2ResourceServerConfigurer(context);
1238+
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
1239+
oauth2ResourceServer
1240+
.jwt()
1241+
.authenticationManager(authenticationManager)
1242+
.decoder(mock(JwtDecoder.class));
1243+
assertThat(oauth2ResourceServer.getAuthenticationManager(http)).isSameAs(authenticationManager);
1244+
1245+
oauth2ResourceServer = new OAuth2ResourceServerConfigurer(context);
1246+
oauth2ResourceServer
1247+
.opaqueToken()
1248+
.authenticationManager(authenticationManager)
1249+
.introspectionClient(mock(OAuth2TokenIntrospectionClient.class));
1250+
assertThat(oauth2ResourceServer.getAuthenticationManager(http)).isSameAs(authenticationManager);
1251+
verify(http, never()).authenticationProvider(any(AuthenticationProvider.class));
1252+
}
1253+
11941254
// -- Incorrect Configuration
11951255

11961256
@Test
@@ -1622,6 +1682,27 @@ public JwtDecoder decoder() {
16221682
}
16231683
}
16241684

1685+
@EnableWebSecurity
1686+
static class JwtAuthenticationManagerConfig extends WebSecurityConfigurerAdapter {
1687+
@Override
1688+
protected void configure(HttpSecurity http) throws Exception {
1689+
// @formatter:off
1690+
http
1691+
.authorizeRequests()
1692+
.anyRequest().authenticated()
1693+
.and()
1694+
.oauth2ResourceServer()
1695+
.jwt()
1696+
.authenticationManager(authenticationProvider()::authenticate);
1697+
// @formatter:on
1698+
}
1699+
1700+
@Bean
1701+
public AuthenticationProvider authenticationProvider() {
1702+
return mock(AuthenticationProvider.class);
1703+
}
1704+
}
1705+
16251706
@EnableWebSecurity
16261707
static class CustomJwtValidatorConfig extends WebSecurityConfigurerAdapter {
16271708
@Autowired
@@ -1735,6 +1816,27 @@ protected void configure(HttpSecurity http) throws Exception {
17351816
}
17361817
}
17371818

1819+
@EnableWebSecurity
1820+
static class OpaqueTokenAuthenticationManagerConfig extends WebSecurityConfigurerAdapter {
1821+
@Override
1822+
protected void configure(HttpSecurity http) throws Exception {
1823+
// @formatter:off
1824+
http
1825+
.authorizeRequests()
1826+
.anyRequest().authenticated()
1827+
.and()
1828+
.oauth2ResourceServer()
1829+
.opaqueToken()
1830+
.authenticationManager(authenticationProvider()::authenticate);
1831+
// @formatter:on
1832+
}
1833+
1834+
@Bean
1835+
public AuthenticationProvider authenticationProvider() {
1836+
return mock(AuthenticationProvider.class);
1837+
}
1838+
}
1839+
17381840
@EnableWebSecurity
17391841
static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter {
17401842
@Override
@@ -1954,6 +2056,14 @@ private void mockRestOperations(String response) {
19542056
.thenReturn(entity);
19552057
}
19562058

2059+
private <T> T bean(Class<T> beanClass) {
2060+
return (T) this.spring.getContext().getBean(beanClass);
2061+
}
2062+
2063+
private <T> T verifyBean(Class<T> beanClass) {
2064+
return (T) verify(this.spring.getContext().getBean(beanClass));
2065+
}
2066+
19572067
private String json(String name) throws IOException {
19582068
return resource(name + ".json");
19592069
}

0 commit comments

Comments
 (0)