|
15 | 15 | */
|
16 | 16 | package org.springframework.security.oauth2.server.authorization.authentication;
|
17 | 17 |
|
18 |
| -import java.time.Instant; |
19 |
| -import java.time.temporal.ChronoUnit; |
20 |
| -import java.util.Base64; |
21 |
| -import java.util.Set; |
22 |
| - |
23 | 18 | import org.springframework.security.authentication.AuthenticationProvider;
|
24 | 19 | import org.springframework.security.core.Authentication;
|
25 | 20 | import org.springframework.security.core.AuthenticationException;
|
|
29 | 24 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
30 | 25 | import org.springframework.security.oauth2.core.OAuth2Error;
|
31 | 26 | 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; |
32 | 39 |
|
33 | 40 | /**
|
34 | 41 | * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Client Credentials Grant.
|
35 | 42 | *
|
36 | 43 | * @author Alexey Nesterov
|
37 | 44 | * @since 0.0.1
|
38 | 45 | * @see OAuth2ClientCredentialsAuthenticationToken
|
| 46 | + * @see OAuth2AccessTokenAuthenticationToken |
| 47 | + * @see OAuth2AuthorizationService |
39 | 48 | * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.4">Section 4.4 Client Credentials Grant</a>
|
40 | 49 | * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.4.2">Section 4.4.2 Access Token Request</a>
|
41 | 50 | */
|
42 |
| - |
43 | 51 | public class OAuth2ClientCredentialsAuthenticationProvider implements AuthenticationProvider {
|
44 |
| - |
| 52 | + private final OAuth2AuthorizationService authorizationService; |
45 | 53 | private final StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
46 | 54 |
|
| 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 | + |
47 | 65 | @Override
|
48 | 66 | public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
49 |
| - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthenticationToken = |
| 67 | + OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = |
50 | 68 | (OAuth2ClientCredentialsAuthenticationToken) authentication;
|
51 | 69 |
|
52 | 70 | 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(); |
55 | 73 | }
|
56 |
| - |
57 | 74 | if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
|
58 | 75 | throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
59 | 76 | }
|
| 77 | + RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); |
60 | 78 |
|
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()); |
69 | 88 | }
|
70 | 89 |
|
71 | 90 | String tokenValue = this.accessTokenGenerator.generateKey();
|
72 | 91 | Instant issuedAt = Instant.now();
|
73 | 92 | Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token lifespan
|
74 | 93 | 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); |
76 | 101 |
|
77 |
| - return new OAuth2AccessTokenAuthenticationToken( |
78 |
| - clientPrincipal.getRegisteredClient(), clientPrincipal, accessToken); |
| 102 | + return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken); |
79 | 103 | }
|
80 | 104 |
|
81 | 105 | @Override
|
|
0 commit comments