Skip to content

Commit ea828fb

Browse files
committed
Polish gh-88
1 parent c40aec2 commit ea828fb

13 files changed

+419
-284
lines changed

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

+17-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
2020
import org.springframework.context.ApplicationContext;
2121
import org.springframework.http.HttpMethod;
22+
import org.springframework.http.HttpStatus;
2223
import org.springframework.security.authentication.AuthenticationManager;
2324
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2425
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
26+
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
2527
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
2628
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
2729
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
@@ -32,6 +34,7 @@
3234
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
3335
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
3436
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
37+
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
3538
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
3639
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3740
import org.springframework.util.Assert;
@@ -89,12 +92,22 @@ public void init(B builder) {
8992
new OAuth2AuthorizationCodeAuthenticationProvider(
9093
getRegisteredClientRepository(builder),
9194
getAuthorizationService(builder));
92-
93-
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider
94-
= new OAuth2ClientCredentialsAuthenticationProvider();
95-
9695
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
96+
97+
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
98+
new OAuth2ClientCredentialsAuthenticationProvider(
99+
getAuthorizationService(builder));
97100
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
101+
102+
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
103+
if (exceptionHandling != null) {
104+
// Register the default AuthenticationEntryPoint for the token endpoint
105+
exceptionHandling.defaultAuthenticationEntryPointFor(
106+
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
107+
new AntPathRequestMatcher(
108+
OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI,
109+
HttpMethod.POST.name()));
110+
}
98111
}
99112

100113
@Override

core/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java

-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
210210
*/
211211
public OAuth2Authorization build() {
212212
Assert.hasText(this.principalName, "principalName cannot be empty");
213-
Assert.notNull(this.attributes.get(OAuth2AuthorizationAttributeNames.CODE), "authorization code cannot be null");
214213

215214
OAuth2Authorization authorization = new OAuth2Authorization();
216215
authorization.registeredClientId = this.registeredClientId;

core/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java

+46-22
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.authentication;
1717

18-
import java.time.Instant;
19-
import java.time.temporal.ChronoUnit;
20-
import java.util.Base64;
21-
import java.util.Set;
22-
2318
import org.springframework.security.authentication.AuthenticationProvider;
2419
import org.springframework.security.core.Authentication;
2520
import org.springframework.security.core.AuthenticationException;
@@ -29,53 +24,82 @@
2924
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3025
import org.springframework.security.oauth2.core.OAuth2Error;
3126
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
27+
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
28+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
29+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.CollectionUtils;
32+
33+
import java.time.Instant;
34+
import java.time.temporal.ChronoUnit;
35+
import java.util.Base64;
36+
import java.util.LinkedHashSet;
37+
import java.util.Set;
38+
import java.util.stream.Collectors;
3239

3340
/**
3441
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Client Credentials Grant.
3542
*
3643
* @author Alexey Nesterov
3744
* @since 0.0.1
3845
* @see OAuth2ClientCredentialsAuthenticationToken
46+
* @see OAuth2AccessTokenAuthenticationToken
47+
* @see OAuth2AuthorizationService
3948
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.4">Section 4.4 Client Credentials Grant</a>
4049
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.4.2">Section 4.4.2 Access Token Request</a>
4150
*/
42-
4351
public class OAuth2ClientCredentialsAuthenticationProvider implements AuthenticationProvider {
44-
52+
private final OAuth2AuthorizationService authorizationService;
4553
private final StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
4654

55+
/**
56+
* Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the provided parameters.
57+
*
58+
* @param authorizationService the authorization service
59+
*/
60+
public OAuth2ClientCredentialsAuthenticationProvider(OAuth2AuthorizationService authorizationService) {
61+
Assert.notNull(authorizationService, "authorizationService cannot be null");
62+
this.authorizationService = authorizationService;
63+
}
64+
4765
@Override
4866
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
49-
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthenticationToken =
67+
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
5068
(OAuth2ClientCredentialsAuthenticationToken) authentication;
5169

5270
OAuth2ClientAuthenticationToken clientPrincipal = null;
53-
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(clientCredentialsAuthenticationToken.getPrincipal().getClass())) {
54-
clientPrincipal = (OAuth2ClientAuthenticationToken) clientCredentialsAuthenticationToken.getPrincipal();
71+
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(clientCredentialsAuthentication.getPrincipal().getClass())) {
72+
clientPrincipal = (OAuth2ClientAuthenticationToken) clientCredentialsAuthentication.getPrincipal();
5573
}
56-
5774
if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
5875
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
5976
}
77+
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
6078

61-
Set<String> clientScopes = clientPrincipal.getRegisteredClient().getScopes();
62-
Set<String> requestedScopes = clientCredentialsAuthenticationToken.getScopes();
63-
if (!clientScopes.containsAll(requestedScopes)) {
64-
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE));
65-
}
66-
67-
if (requestedScopes == null || requestedScopes.isEmpty()) {
68-
requestedScopes = clientScopes;
79+
Set<String> scopes = registeredClient.getScopes(); // Default to configured scopes
80+
if (!CollectionUtils.isEmpty(clientCredentialsAuthentication.getScopes())) {
81+
Set<String> unauthorizedScopes = clientCredentialsAuthentication.getScopes().stream()
82+
.filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope))
83+
.collect(Collectors.toSet());
84+
if (!CollectionUtils.isEmpty(unauthorizedScopes)) {
85+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE));
86+
}
87+
scopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
6988
}
7089

7190
String tokenValue = this.accessTokenGenerator.generateKey();
7291
Instant issuedAt = Instant.now();
7392
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token lifespan
7493
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
75-
tokenValue, issuedAt, expiresAt, requestedScopes);
94+
tokenValue, issuedAt, expiresAt, scopes);
95+
96+
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
97+
.principalName(clientPrincipal.getName())
98+
.accessToken(accessToken)
99+
.build();
100+
this.authorizationService.save(authorization);
76101

77-
return new OAuth2AccessTokenAuthenticationToken(
78-
clientPrincipal.getRegisteredClient(), clientPrincipal, accessToken);
102+
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
79103
}
80104

81105
@Override

core/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java

+30-16
Original file line numberDiff line numberDiff line change
@@ -13,55 +13,69 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package org.springframework.security.oauth2.server.authorization.authentication;
1817

19-
import java.util.Collections;
20-
import java.util.Set;
21-
2218
import org.springframework.security.authentication.AbstractAuthenticationToken;
2319
import org.springframework.security.core.Authentication;
2420
import org.springframework.security.oauth2.server.authorization.Version;
2521
import org.springframework.util.Assert;
2622

23+
import java.util.Collections;
24+
import java.util.LinkedHashSet;
25+
import java.util.Set;
26+
2727
/**
2828
* An {@link Authentication} implementation used for the OAuth 2.0 Client Credentials Grant.
2929
*
3030
* @author Alexey Nesterov
3131
* @since 0.0.1
32-
* @see Authentication
32+
* @see AbstractAuthenticationToken
3333
* @see OAuth2ClientCredentialsAuthenticationProvider
34+
* @see OAuth2ClientAuthenticationToken
3435
*/
3536
public class OAuth2ClientCredentialsAuthenticationToken extends AbstractAuthenticationToken {
36-
3737
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
38-
3938
private final Authentication clientPrincipal;
4039
private final Set<String> scopes;
4140

41+
/**
42+
* Constructs an {@code OAuth2ClientCredentialsAuthenticationToken} using the provided parameters.
43+
*
44+
* @param clientPrincipal the authenticated client principal
45+
*/
46+
public OAuth2ClientCredentialsAuthenticationToken(Authentication clientPrincipal) {
47+
this(clientPrincipal, Collections.emptySet());
48+
}
49+
50+
/**
51+
* Constructs an {@code OAuth2ClientCredentialsAuthenticationToken} using the provided parameters.
52+
*
53+
* @param clientPrincipal the authenticated client principal
54+
* @param scopes the requested scope(s)
55+
*/
4256
public OAuth2ClientCredentialsAuthenticationToken(Authentication clientPrincipal, Set<String> scopes) {
4357
super(Collections.emptyList());
4458
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
4559
Assert.notNull(scopes, "scopes cannot be null");
4660
this.clientPrincipal = clientPrincipal;
47-
this.scopes = scopes;
61+
this.scopes = Collections.unmodifiableSet(new LinkedHashSet<>(scopes));
4862
}
4963

50-
@SuppressWarnings("unchecked")
51-
public OAuth2ClientCredentialsAuthenticationToken(OAuth2ClientAuthenticationToken clientPrincipal) {
52-
this(clientPrincipal, Collections.EMPTY_SET);
64+
@Override
65+
public Object getPrincipal() {
66+
return this.clientPrincipal;
5367
}
5468

5569
@Override
5670
public Object getCredentials() {
5771
return "";
5872
}
5973

60-
@Override
61-
public Object getPrincipal() {
62-
return this.clientPrincipal;
63-
}
64-
74+
/**
75+
* Returns the requested scope(s).
76+
*
77+
* @return the requested scope(s), or an empty {@code Set} if not available
78+
*/
6579
public Set<String> getScopes() {
6680
return this.scopes;
6781
}

core/src/main/java/org/springframework/security/oauth2/server/authorization/web/DelegatingAuthorizationGrantAuthenticationConverter.java

+19-12
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,52 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package org.springframework.security.oauth2.server.authorization.web;
1817

19-
import javax.servlet.http.HttpServletRequest;
20-
import java.util.Collections;
21-
import java.util.Map;
22-
2318
import org.springframework.core.convert.converter.Converter;
2419
import org.springframework.security.core.Authentication;
2520
import org.springframework.security.oauth2.core.AuthorizationGrantType;
2621
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
2722
import org.springframework.util.Assert;
2823
import org.springframework.util.StringUtils;
2924

25+
import javax.servlet.http.HttpServletRequest;
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
3030
/**
31-
* A {@link Converter} that delegates actual conversion to one of the provided converters based on grant_type param of a request.
32-
* Returns null is grant type is not specified or not supported.
31+
* A {@link Converter} that selects (and delegates) to one of the internal {@code Map} of {@link Converter}'s
32+
* using the {@link OAuth2ParameterNames#GRANT_TYPE} request parameter.
3333
*
3434
* @author Alexey Nesterov
3535
* @since 0.0.1
3636
*/
3737
public final class DelegatingAuthorizationGrantAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
38-
3938
private final Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters;
4039

41-
public DelegatingAuthorizationGrantAuthenticationConverter(Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters) {
40+
/**
41+
* Constructs a {@code DelegatingAuthorizationGrantAuthenticationConverter} using the provided parameters.
42+
*
43+
* @param converters a {@code Map} of {@link Converter}(s)
44+
*/
45+
public DelegatingAuthorizationGrantAuthenticationConverter(
46+
Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters) {
4247
Assert.notEmpty(converters, "converters cannot be empty");
43-
44-
this.converters = Collections.unmodifiableMap(converters);
48+
this.converters = Collections.unmodifiableMap(new HashMap<>(converters));
4549
}
4650

4751
@Override
4852
public Authentication convert(HttpServletRequest request) {
53+
Assert.notNull(request, "request cannot be null");
54+
4955
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
5056
if (StringUtils.isEmpty(grantType)) {
5157
return null;
5258
}
5359

54-
Converter<HttpServletRequest, Authentication> converter = this.converters.get(new AuthorizationGrantType(grantType));
60+
Converter<HttpServletRequest, Authentication> converter =
61+
this.converters.get(new AuthorizationGrantType(grantType));
5562
if (converter == null) {
5663
return null;
5764
}

0 commit comments

Comments
 (0)