Skip to content

Commit ef9c3e4

Browse files
committed
Opaque Token Support
Fixes: gh-5200
1 parent 594a169 commit ef9c3e4

File tree

9 files changed

+996
-20
lines changed

9 files changed

+996
-20
lines changed

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

Lines changed: 66 additions & 15 deletions
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-2019 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.
@@ -22,6 +22,7 @@
2222
import org.springframework.core.convert.converter.Converter;
2323
import org.springframework.security.authentication.AbstractAuthenticationToken;
2424
import org.springframework.security.authentication.AuthenticationManager;
25+
import org.springframework.security.authentication.AuthenticationProvider;
2526
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2627
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
2728
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
@@ -31,6 +32,7 @@
3132
import org.springframework.security.oauth2.jwt.Jwt;
3233
import org.springframework.security.oauth2.jwt.JwtDecoder;
3334
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
35+
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationProvider;
3436
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
3537
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
3638
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
@@ -86,6 +88,10 @@
8688
* </li>
8789
* </ul>
8890
*
91+
* <p>
92+
* When using {@link #opaque()}, supply an introspection endpoint and its authentication configuration
93+
* </p>
94+
*
8995
* <h2>Security Filters</h2>
9096
*
9197
* The following {@code Filter}s are populated when {@link #jwt()} is configured:
@@ -123,7 +129,9 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
123129
private final ApplicationContext context;
124130

125131
private BearerTokenResolver bearerTokenResolver;
132+
126133
private JwtConfigurer jwtConfigurer;
134+
private OpaqueTokenConfigurer opaqueTokenConfigurer;
127135

128136
private AccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler();
129137
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
@@ -160,6 +168,14 @@ public JwtConfigurer jwt() {
160168
return this.jwtConfigurer;
161169
}
162170

171+
public OpaqueTokenConfigurer opaqueToken() {
172+
if (this.opaqueTokenConfigurer == null) {
173+
this.opaqueTokenConfigurer = new OpaqueTokenConfigurer();
174+
}
175+
176+
return this.opaqueTokenConfigurer;
177+
}
178+
163179
@Override
164180
public void init(H http) throws Exception {
165181
registerDefaultAccessDeniedHandler(http);
@@ -182,24 +198,34 @@ public void configure(H http) throws Exception {
182198

183199
http.addFilter(filter);
184200

185-
if ( this.jwtConfigurer == null ) {
186-
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
187-
"in Spring Security and no Jwt configuration was found. Make sure to specify " +
188-
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
189-
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
190-
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
201+
if (this.jwtConfigurer != null && this.opaqueTokenConfigurer != null) {
202+
throw new IllegalStateException("Spring Security only supports JWTs or Opaque Tokens, not both at the " +
203+
"same time");
204+
}
205+
206+
if (this.jwtConfigurer == null && this.opaqueTokenConfigurer == null) {
207+
throw new IllegalStateException("Jwt and Opaque Token are the only supported formats for bearer tokens " +
208+
"in Spring Security and neither was found. Make sure to configure JWT " +
209+
"via http.oauth2ResourceServer().jwt() or Opaque Tokens via " +
210+
"http.oauth2ResourceServer().opaque().");
191211
}
192212

193-
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
194-
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
195-
this.jwtConfigurer.getJwtAuthenticationConverter();
213+
if (this.jwtConfigurer != null) {
214+
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
215+
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
216+
this.jwtConfigurer.getJwtAuthenticationConverter();
217+
218+
JwtAuthenticationProvider provider =
219+
new JwtAuthenticationProvider(decoder);
220+
provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
221+
provider = postProcess(provider);
196222

197-
JwtAuthenticationProvider provider =
198-
new JwtAuthenticationProvider(decoder);
199-
provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
200-
provider = postProcess(provider);
223+
http.authenticationProvider(provider);
224+
}
201225

202-
http.authenticationProvider(provider);
226+
if (this.opaqueTokenConfigurer != null) {
227+
http.authenticationProvider(this.opaqueTokenConfigurer.getProvider());
228+
}
203229
}
204230

205231
public class JwtConfigurer {
@@ -248,6 +274,31 @@ JwtDecoder getJwtDecoder() {
248274
}
249275
}
250276

277+
public class OpaqueTokenConfigurer {
278+
private String introspectionUri;
279+
private String introspectionClientId;
280+
private String introspectionClientSecret;
281+
282+
public OpaqueTokenConfigurer introspectionUri(String introspectionUri) {
283+
Assert.notNull(introspectionUri, "introspectionUri cannot be null");
284+
this.introspectionUri = introspectionUri;
285+
return this;
286+
}
287+
288+
public OpaqueTokenConfigurer introspectionClientCredentials(String clientId, String clientSecret) {
289+
Assert.notNull(clientId, "clientId cannot be null");
290+
Assert.notNull(clientSecret, "clientSecret cannot be null");
291+
this.introspectionClientId = clientId;
292+
this.introspectionClientSecret = clientSecret;
293+
return this;
294+
}
295+
296+
AuthenticationProvider getProvider() {
297+
return new OAuth2IntrospectionAuthenticationProvider(this.introspectionUri,
298+
this.introspectionClientId, this.introspectionClientSecret);
299+
}
300+
}
301+
251302
private void registerDefaultAccessDeniedHandler(H http) {
252303
ExceptionHandlingConfigurer<H> exceptionHandling = http
253304
.getConfigurer(ExceptionHandlingConfigurer.class);

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

Lines changed: 22 additions & 2 deletions
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-2019 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.
@@ -1109,7 +1109,7 @@ public void configuredWhenMissingJwtAuthenticationProviderThenWiringException()
11091109

11101110
assertThatCode(() -> this.spring.register(JwtlessConfig.class).autowire())
11111111
.isInstanceOf(BeanCreationException.class)
1112-
.hasMessageContaining("no Jwt configuration was found");
1112+
.hasMessageContaining("neither was found");
11131113
}
11141114

11151115
@Test
@@ -1120,6 +1120,13 @@ public void configureWhenMissingJwkSetUriThenWiringException() {
11201120
.hasMessageContaining("No qualifying bean of type");
11211121
}
11221122

1123+
@Test
1124+
public void configureWhenUsingBothJwtAndOpaqueThenWiringException() {
1125+
assertThatCode(() -> this.spring.register(OpaqueAndJwtConfig.class).autowire())
1126+
.isInstanceOf(BeanCreationException.class)
1127+
.hasMessageContaining("Spring Security only supports JWTs or Opaque Tokens");
1128+
}
1129+
11231130
// -- support
11241131

11251132
@EnableWebSecurity
@@ -1623,6 +1630,19 @@ JwtDecoder decoder() throws Exception {
16231630
}
16241631
}
16251632

1633+
@EnableWebSecurity
1634+
static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter {
1635+
@Override
1636+
protected void configure(HttpSecurity http) throws Exception {
1637+
// @formatter:off
1638+
http
1639+
.oauth2ResourceServer()
1640+
.jwt()
1641+
.and()
1642+
.opaqueToken();
1643+
}
1644+
}
1645+
16261646
@Configuration
16271647
static class JwtDecoderConfig {
16281648
@Bean

oauth2/oauth2-resource-server/spring-security-oauth2-resource-server.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dependencies {
77
compile springCoreDependency
88

99
optional project(':spring-security-oauth2-jose')
10+
optional 'com.nimbusds:oauth2-oidc-sdk'
1011
optional 'io.projectreactor:reactor-core'
1112
optional 'org.springframework:spring-webflux'
1213

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/AbstractOAuth2TokenAuthenticationToken.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
public abstract class AbstractOAuth2TokenAuthenticationToken<T extends AbstractOAuth2Token> extends AbstractAuthenticationToken {
4646
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
4747

48+
private Object principal;
49+
private Object credentials;
4850
private T token;
4951

5052
/**
@@ -64,9 +66,20 @@ protected AbstractOAuth2TokenAuthenticationToken(
6466
T token,
6567
Collection<? extends GrantedAuthority> authorities) {
6668

67-
super(authorities);
69+
this(token, token, token, authorities);
70+
}
6871

72+
protected AbstractOAuth2TokenAuthenticationToken(
73+
T token,
74+
Object principal,
75+
Object credentials,
76+
Collection<? extends GrantedAuthority> authorities) {
77+
78+
super(authorities);
6979
Assert.notNull(token, "token cannot be null");
80+
Assert.notNull(principal, "principal cannot be null");
81+
this.principal = principal;
82+
this.credentials = credentials;
7083
this.token = token;
7184
}
7285

@@ -75,15 +88,15 @@ protected AbstractOAuth2TokenAuthenticationToken(
7588
*/
7689
@Override
7790
public Object getPrincipal() {
78-
return this.getToken();
91+
return this.principal;
7992
}
8093

8194
/**
8295
* {@inheritDoc}
8396
*/
8497
@Override
8598
public Object getCredentials() {
86-
return this.getToken();
99+
return this.credentials;
87100
}
88101

89102
/**

0 commit comments

Comments
 (0)