Skip to content

Commit 3241445

Browse files
committed
Add support for opaque access tokens
Closes gh-500
1 parent a661e1c commit 3241445

File tree

30 files changed

+1591
-101
lines changed

30 files changed

+1591
-101
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import javax.servlet.http.HttpServletRequestWrapper;
2727
import javax.servlet.http.HttpServletResponse;
2828

29+
import com.nimbusds.jose.jwk.source.JWKSource;
30+
2931
import org.springframework.core.annotation.AnnotationUtils;
3032
import org.springframework.http.HttpMethod;
3133
import org.springframework.http.HttpStatus;
@@ -367,11 +369,12 @@ public void configure(B builder) {
367369
providerSettings.getTokenIntrospectionEndpoint());
368370
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), FilterSecurityInterceptor.class);
369371

370-
NimbusJwkSetEndpointFilter jwkSetEndpointFilter =
371-
new NimbusJwkSetEndpointFilter(
372-
OAuth2ConfigurerUtils.getJwkSource(builder),
373-
providerSettings.getJwkSetEndpoint());
374-
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
372+
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
373+
if (jwkSource != null) {
374+
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
375+
jwkSource, providerSettings.getJwkSetEndpoint());
376+
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
377+
}
375378

376379
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
377380
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);

oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@
3434
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
3535
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
3636
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
37+
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
3738
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
3839
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
3940
import org.springframework.security.oauth2.server.authorization.OAuth2RefreshTokenGenerator;
41+
import org.springframework.security.oauth2.server.authorization.OAuth2TokenClaimsContext;
4042
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
4143
import org.springframework.security.oauth2.server.authorization.OAuth2TokenGenerator;
4244
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -93,28 +95,55 @@ static <B extends HttpSecurityBuilder<B>> OAuth2TokenGenerator<? extends OAuth2T
9395
if (tokenGenerator == null) {
9496
tokenGenerator = getOptionalBean(builder, OAuth2TokenGenerator.class);
9597
if (tokenGenerator == null) {
96-
JwtGenerator jwtGenerator = new JwtGenerator(getJwtEncoder(builder));
97-
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
98-
if (jwtCustomizer != null) {
99-
jwtGenerator.setJwtCustomizer(jwtCustomizer);
98+
JwtGenerator jwtGenerator = getJwtGenerator(builder);
99+
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
100+
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(builder);
101+
if (accessTokenCustomizer != null) {
102+
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);
100103
}
101104
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
102-
tokenGenerator = new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator);
105+
if (jwtGenerator != null) {
106+
tokenGenerator = new DelegatingOAuth2TokenGenerator(
107+
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
108+
} else {
109+
tokenGenerator = new DelegatingOAuth2TokenGenerator(
110+
accessTokenGenerator, refreshTokenGenerator);
111+
}
103112
}
104113
builder.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
105114
}
106115
return tokenGenerator;
107116
}
108117

118+
private static <B extends HttpSecurityBuilder<B>> JwtGenerator getJwtGenerator(B builder) {
119+
JwtGenerator jwtGenerator = builder.getSharedObject(JwtGenerator.class);
120+
if (jwtGenerator == null) {
121+
JwtEncoder jwtEncoder = getJwtEncoder(builder);
122+
if (jwtEncoder != null) {
123+
jwtGenerator = new JwtGenerator(jwtEncoder);
124+
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
125+
if (jwtCustomizer != null) {
126+
jwtGenerator.setJwtCustomizer(jwtCustomizer);
127+
}
128+
builder.setSharedObject(JwtGenerator.class, jwtGenerator);
129+
}
130+
}
131+
return jwtGenerator;
132+
}
133+
109134
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
110135
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
111136
if (jwtEncoder == null) {
112137
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
113138
if (jwtEncoder == null) {
114139
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
115-
jwtEncoder = new NimbusJwsEncoder(jwkSource);
140+
if (jwkSource != null) {
141+
jwtEncoder = new NimbusJwsEncoder(jwkSource);
142+
}
143+
}
144+
if (jwtEncoder != null) {
145+
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
116146
}
117-
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
118147
}
119148
return jwtEncoder;
120149
}
@@ -124,23 +153,22 @@ static <B extends HttpSecurityBuilder<B>> JWKSource<SecurityContext> getJwkSourc
124153
JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
125154
if (jwkSource == null) {
126155
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
127-
jwkSource = getBean(builder, type);
128-
builder.setSharedObject(JWKSource.class, jwkSource);
156+
jwkSource = getOptionalBean(builder, type);
157+
if (jwkSource != null) {
158+
builder.setSharedObject(JWKSource.class, jwkSource);
159+
}
129160
}
130161
return jwkSource;
131162
}
132163

133-
@SuppressWarnings("unchecked")
134164
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
135-
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = builder.getSharedObject(OAuth2TokenCustomizer.class);
136-
if (jwtCustomizer == null) {
137-
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
138-
jwtCustomizer = getOptionalBean(builder, type);
139-
if (jwtCustomizer != null) {
140-
builder.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);
141-
}
142-
}
143-
return jwtCustomizer;
165+
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
166+
return getOptionalBean(builder, type);
167+
}
168+
169+
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(B builder) {
170+
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
171+
return getOptionalBean(builder, type);
144172
}
145173

146174
static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core;
17+
18+
import java.net.URL;
19+
import java.time.Instant;
20+
import java.util.List;
21+
22+
/**
23+
* A {@link ClaimAccessor} for the "claims" that may be contained in an {@link OAuth2TokenClaimsSet}.
24+
*
25+
* @author Joe Grandja
26+
* @since 0.2.3
27+
* @see ClaimAccessor
28+
* @see OAuth2TokenClaimNames
29+
* @see OAuth2TokenClaimsSet
30+
*/
31+
public interface OAuth2TokenClaimAccessor extends ClaimAccessor {
32+
33+
/**
34+
* Returns the Issuer {@code (iss)} claim which identifies the principal that issued the OAuth 2.0 Token.
35+
*
36+
* @return the Issuer identifier
37+
*/
38+
default URL getIssuer() {
39+
return getClaimAsURL(OAuth2TokenClaimNames.ISS);
40+
}
41+
42+
/**
43+
* Returns the Subject {@code (sub)} claim which identifies the principal that is the subject of the OAuth 2.0 Token.
44+
*
45+
* @return the Subject identifier
46+
*/
47+
default String getSubject() {
48+
return getClaimAsString(OAuth2TokenClaimNames.SUB);
49+
}
50+
51+
/**
52+
* Returns the Audience {@code (aud)} claim which identifies the recipient(s) that the OAuth 2.0 Token is intended for.
53+
*
54+
* @return the Audience(s) that this OAuth 2.0 Token is intended for
55+
*/
56+
default List<String> getAudience() {
57+
return getClaimAsStringList(OAuth2TokenClaimNames.AUD);
58+
}
59+
60+
/**
61+
* Returns the Expiration time {@code (exp)} claim which identifies the expiration time on or after
62+
* which the OAuth 2.0 Token MUST NOT be accepted for processing.
63+
*
64+
* @return the Expiration time on or after which the OAuth 2.0 Token MUST NOT be accepted for processing
65+
*/
66+
default Instant getExpiresAt() {
67+
return getClaimAsInstant(OAuth2TokenClaimNames.EXP);
68+
}
69+
70+
/**
71+
* Returns the Not Before {@code (nbf)} claim which identifies the time before
72+
* which the OAuth 2.0 Token MUST NOT be accepted for processing.
73+
*
74+
* @return the Not Before time before which the OAuth 2.0 Token MUST NOT be accepted for processing
75+
*/
76+
default Instant getNotBefore() {
77+
return getClaimAsInstant(OAuth2TokenClaimNames.NBF);
78+
}
79+
80+
/**
81+
* Returns the Issued at {@code (iat)} claim which identifies the time at which the OAuth 2.0 Token was issued.
82+
*
83+
* @return the Issued at claim which identifies the time at which the OAuth 2.0 Token was issued
84+
*/
85+
default Instant getIssuedAt() {
86+
return getClaimAsInstant(OAuth2TokenClaimNames.IAT);
87+
}
88+
89+
/**
90+
* Returns the ID {@code (jti)} claim which provides a unique identifier for the OAuth 2.0 Token.
91+
*
92+
* @return the ID claim which provides a unique identifier for the OAuth 2.0 Token
93+
*/
94+
default String getId() {
95+
return getClaimAsString(OAuth2TokenClaimNames.JTI);
96+
}
97+
98+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core;
17+
18+
/**
19+
* The names of the "claims" that may be contained in an {@link OAuth2TokenClaimsSet}
20+
* and are associated to an {@link OAuth2Token}.
21+
*
22+
* @author Joe Grandja
23+
* @since 0.2.3
24+
* @see OAuth2TokenClaimAccessor
25+
* @see OAuth2TokenClaimsSet
26+
* @see OAuth2Token
27+
*/
28+
public interface OAuth2TokenClaimNames {
29+
30+
/**
31+
* {@code iss} - the Issuer claim identifies the principal that issued the OAuth 2.0 Token
32+
*/
33+
String ISS = "iss";
34+
35+
/**
36+
* {@code sub} - the Subject claim identifies the principal that is the subject of the OAuth 2.0 Token
37+
*/
38+
String SUB = "sub";
39+
40+
/**
41+
* {@code aud} - the Audience claim identifies the recipient(s) that the OAuth 2.0 Token is intended for
42+
*/
43+
String AUD = "aud";
44+
45+
/**
46+
* {@code exp} - the Expiration time claim identifies the expiration time on or after
47+
* which the OAuth 2.0 Token MUST NOT be accepted for processing
48+
*/
49+
String EXP = "exp";
50+
51+
/**
52+
* {@code nbf} - the Not Before claim identifies the time before which the OAuth 2.0 Token
53+
* MUST NOT be accepted for processing
54+
*/
55+
String NBF = "nbf";
56+
57+
/**
58+
* {@code iat} - The Issued at claim identifies the time at which the OAuth 2.0 Token was issued
59+
*/
60+
String IAT = "iat";
61+
62+
/**
63+
* {@code jti} - The ID claim provides a unique identifier for the OAuth 2.0 Token
64+
*/
65+
String JTI = "jti";
66+
67+
}

0 commit comments

Comments
 (0)