Skip to content

Commit 7319048

Browse files
committed
Polish gh-9505
1 parent aa40603 commit 7319048

17 files changed

+425
-568
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java

+34-54
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,20 @@
1616

1717
package org.springframework.security.oauth2.client;
1818

19-
import java.time.Clock;
20-
import java.time.Duration;
21-
import java.time.Instant;
22-
2319
import org.springframework.lang.Nullable;
2420
import org.springframework.security.oauth2.client.endpoint.DefaultJwtBearerTokenResponseClient;
21+
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
2522
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
26-
import org.springframework.security.oauth2.client.endpoint.OAuth2JwtBearerGrantRequest;
2723
import org.springframework.security.oauth2.client.registration.ClientRegistration;
28-
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
24+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
25+
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
2926
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
3027
import org.springframework.security.oauth2.jwt.Jwt;
3128
import org.springframework.util.Assert;
3229

3330
/**
3431
* An implementation of an {@link OAuth2AuthorizedClientProvider} for the
35-
* {@link OAuth2JwtBearerGrantRequest#JWT_BEARER_GRANT_TYPE jwt-bearer} grant.
32+
* {@link AuthorizationGrantType#JWT_BEARER jwt-bearer} grant.
3633
*
3734
* @author Joe Grandja
3835
* @since 5.5
@@ -41,18 +38,14 @@
4138
*/
4239
public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
4340

44-
private OAuth2AccessTokenResponseClient<OAuth2JwtBearerGrantRequest> accessTokenResponseClient = new DefaultJwtBearerTokenResponseClient();
45-
46-
private Duration clockSkew = Duration.ofSeconds(60);
47-
48-
private Clock clock = Clock.systemUTC();
41+
private OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = new DefaultJwtBearerTokenResponseClient();
4942

5043
/**
5144
* Attempt to authorize the {@link OAuth2AuthorizationContext#getClientRegistration()
5245
* client} in the provided {@code context}. Returns {@code null} if authorization is
5346
* not supported, e.g. the client's
5447
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is
55-
* not {@link OAuth2JwtBearerGrantRequest#JWT_BEARER_GRANT_TYPE jwt-bearer}.
48+
* not {@link AuthorizationGrantType#JWT_BEARER jwt-bearer}.
5649
* @param context the context that holds authorization-specific state for the client
5750
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not
5851
* supported
@@ -61,34 +54,44 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth
6154
@Nullable
6255
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
6356
Assert.notNull(context, "context cannot be null");
64-
6557
ClientRegistration clientRegistration = context.getClientRegistration();
66-
if (!OAuth2JwtBearerGrantRequest.JWT_BEARER_GRANT_TYPE.equals(clientRegistration.getAuthorizationGrantType())) {
58+
if (!AuthorizationGrantType.JWT_BEARER.equals(clientRegistration.getAuthorizationGrantType())) {
6759
return null;
6860
}
69-
70-
Jwt jwt = context.getAttribute(OAuth2AuthorizationContext.JWT_ATTRIBUTE_NAME);
71-
if (jwt == null) {
61+
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
62+
if (authorizedClient != null) {
7263
return null;
7364
}
74-
75-
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
76-
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
77-
// If client is already authorized but access token is NOT expired than no
78-
// need for re-authorization
65+
if (!(context.getPrincipal().getPrincipal() instanceof Jwt)) {
7966
return null;
8067
}
81-
82-
OAuth2JwtBearerGrantRequest jwtBearerGrantRequest = new OAuth2JwtBearerGrantRequest(clientRegistration, jwt);
83-
OAuth2AccessTokenResponse tokenResponse = this.accessTokenResponseClient
84-
.getTokenResponse(jwtBearerGrantRequest);
85-
68+
Jwt jwt = (Jwt) context.getPrincipal().getPrincipal();
69+
// As per spec, in section 4.1 Using Assertions as Authorization Grants
70+
// https://tools.ietf.org/html/rfc7521#section-4.1
71+
//
72+
// An assertion used in this context is generally a short-lived
73+
// representation of the authorization grant, and authorization servers
74+
// SHOULD NOT issue access tokens with a lifetime that exceeds the
75+
// validity period of the assertion by a significant period. In
76+
// practice, that will usually mean that refresh tokens are not issued
77+
// in response to assertion grant requests, and access tokens will be
78+
// issued with a reasonably short lifetime. Clients can refresh an
79+
// expired access token by requesting a new one using the same
80+
// assertion, if it is still valid, or with a new assertion.
81+
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, jwt);
82+
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, jwtBearerGrantRequest);
8683
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
8784
tokenResponse.getAccessToken());
8885
}
8986

90-
private boolean hasTokenExpired(AbstractOAuth2Token token) {
91-
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
87+
private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration,
88+
JwtBearerGrantRequest jwtBearerGrantRequest) {
89+
try {
90+
return this.accessTokenResponseClient.getTokenResponse(jwtBearerGrantRequest);
91+
}
92+
catch (OAuth2AuthorizationException ex) {
93+
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
94+
}
9295
}
9396

9497
/**
@@ -98,32 +101,9 @@ private boolean hasTokenExpired(AbstractOAuth2Token token) {
98101
* credential at the Token Endpoint for the {@code jwt-bearer} grant
99102
*/
100103
public void setAccessTokenResponseClient(
101-
OAuth2AccessTokenResponseClient<OAuth2JwtBearerGrantRequest> accessTokenResponseClient) {
104+
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient) {
102105
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
103106
this.accessTokenResponseClient = accessTokenResponseClient;
104107
}
105108

106-
/**
107-
* Sets the maximum acceptable clock skew, which is used when checking the
108-
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
109-
* 60 seconds. An access token is considered expired if it's before
110-
* {@code Instant.now(this.clock) - clockSkew}.
111-
* @param clockSkew the maximum acceptable clock skew
112-
*/
113-
public void setClockSkew(Duration clockSkew) {
114-
Assert.notNull(clockSkew, "clockSkew cannot be null");
115-
Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0");
116-
this.clockSkew = clockSkew;
117-
}
118-
119-
/**
120-
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
121-
* token expiry.
122-
* @param clock the clock
123-
*/
124-
public void setClock(Clock clock) {
125-
Assert.notNull(clock, "clock cannot be null");
126-
this.clock = clock;
127-
}
128-
129109
}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 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.
@@ -60,12 +60,6 @@ public final class OAuth2AuthorizationContext {
6060
*/
6161
public static final String PASSWORD_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".PASSWORD");
6262

63-
/**
64-
* The name of the {@link #getAttribute(String) attribute} in the context associated
65-
* to the value for the JWT Bearer token.
66-
*/
67-
public static final String JWT_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".JWT");
68-
6963
private ClientRegistration clientRegistration;
7064

7165
private OAuth2AuthorizedClient authorizedClient;

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java

+2-97
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 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.
@@ -27,7 +27,6 @@
2727

2828
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
2929
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
30-
import org.springframework.security.oauth2.client.endpoint.OAuth2JwtBearerGrantRequest;
3130
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
3231
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
3332
import org.springframework.util.Assert;
@@ -157,29 +156,6 @@ public OAuth2AuthorizedClientProviderBuilder password(Consumer<PasswordGrantBuil
157156
return OAuth2AuthorizedClientProviderBuilder.this;
158157
}
159158

160-
/**
161-
* Configures support for the {@code jwt_bearer} grant.
162-
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
163-
*/
164-
public OAuth2AuthorizedClientProviderBuilder jwtBearer() {
165-
this.builders.computeIfAbsent(JwtBearerOAuth2AuthorizedClientProvider.class,
166-
(k) -> new JwtBearerGrantBuilder());
167-
return OAuth2AuthorizedClientProviderBuilder.this;
168-
}
169-
170-
/**
171-
* Configures support for the {@code jwt_bearer} grant.
172-
* @param builderConsumer a {@code Consumer} of {@link JwtBearerGrantBuilder} used for
173-
* further configuration
174-
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
175-
*/
176-
public OAuth2AuthorizedClientProviderBuilder jwtBearer(Consumer<JwtBearerGrantBuilder> builderConsumer) {
177-
JwtBearerGrantBuilder builder = (JwtBearerGrantBuilder) this.builders
178-
.computeIfAbsent(JwtBearerOAuth2AuthorizedClientProvider.class, (k) -> new JwtBearerGrantBuilder());
179-
builderConsumer.accept(builder);
180-
return OAuth2AuthorizedClientProviderBuilder.this;
181-
}
182-
183159
/**
184160
* Builds an instance of {@link DelegatingOAuth2AuthorizedClientProvider} composed of
185161
* one or more {@link OAuth2AuthorizedClientProvider}(s).
@@ -229,7 +205,7 @@ public PasswordGrantBuilder accessTokenResponseClient(
229205
/**
230206
* Sets the maximum acceptable clock skew, which is used when checking the access
231207
* token expiry. An access token is considered expired if it's before
232-
* {@code Instant.now(this.clock) + clockSkew}.
208+
* {@code Instant.now(this.clock) - clockSkew}.
233209
* @param clockSkew the maximum acceptable clock skew
234210
* @return the {@link PasswordGrantBuilder}
235211
*/
@@ -270,77 +246,6 @@ public OAuth2AuthorizedClientProvider build() {
270246

271247
}
272248

273-
/**
274-
* A builder for the {@code jwt_bearer} grant.
275-
*/
276-
public final class JwtBearerGrantBuilder implements Builder {
277-
278-
private OAuth2AccessTokenResponseClient<OAuth2JwtBearerGrantRequest> accessTokenResponseClient;
279-
280-
private Duration clockSkew;
281-
282-
private Clock clock;
283-
284-
private JwtBearerGrantBuilder() {
285-
}
286-
287-
/**
288-
* Sets the client used when requesting an access token credential at the Token
289-
* Endpoint.
290-
* @param accessTokenResponseClient the client used when requesting an access
291-
* token credential at the Token Endpoint
292-
* @return the {@link JwtBearerGrantBuilder}
293-
*/
294-
public JwtBearerGrantBuilder accessTokenResponseClient(
295-
OAuth2AccessTokenResponseClient<OAuth2JwtBearerGrantRequest> accessTokenResponseClient) {
296-
this.accessTokenResponseClient = accessTokenResponseClient;
297-
return this;
298-
}
299-
300-
/**
301-
* Sets the maximum acceptable clock skew, which is used when checking the access
302-
* token expiry. An access token is considered expired if it's before
303-
* {@code Instant.now(this.clock) + clockSkew}.
304-
* @param clockSkew the maximum acceptable clock skew
305-
* @return the {@link JwtBearerGrantBuilder}
306-
*/
307-
public JwtBearerGrantBuilder clockSkew(Duration clockSkew) {
308-
this.clockSkew = clockSkew;
309-
return this;
310-
}
311-
312-
/**
313-
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
314-
* access token expiry.
315-
* @param clock the clock
316-
* @return the {@link JwtBearerGrantBuilder}
317-
*/
318-
public JwtBearerGrantBuilder clock(Clock clock) {
319-
this.clock = clock;
320-
return this;
321-
}
322-
323-
/**
324-
* Builds an instance of {@link JwtBearerOAuth2AuthorizedClientProvider}.
325-
* @return the {@link JwtBearerOAuth2AuthorizedClientProvider}
326-
*/
327-
@Override
328-
public OAuth2AuthorizedClientProvider build() {
329-
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
330-
if (this.accessTokenResponseClient != null) {
331-
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
332-
}
333-
if (this.clockSkew != null) {
334-
authorizedClientProvider.setClockSkew(this.clockSkew);
335-
}
336-
if (this.clock != null) {
337-
authorizedClientProvider.setClock(this.clock);
338-
}
339-
return authorizedClientProvider;
340-
}
341-
342-
}
343-
344249
/**
345250
* A builder for the {@code client_credentials} grant.
346251
*/

0 commit comments

Comments
 (0)