/* * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.oauth2.server.authorization.authentication; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.jose.JoseHeader; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.TokenType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; /** * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Code Grant. * * @author Joe Grandja * @author Daniel Garnier-Moiroux * @since 0.0.1 * @see OAuth2AuthorizationCodeAuthenticationToken * @see OAuth2AccessTokenAuthenticationToken * @see RegisteredClientRepository * @see OAuth2AuthorizationService * @see JwtEncoder * @see Section 4.1 Authorization Code Grant * @see Section 4.1.3 Access Token Request */ public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider { private final RegisteredClientRepository registeredClientRepository; private final OAuth2AuthorizationService authorizationService; private final JwtEncoder jwtEncoder; /** * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters. * * @param registeredClientRepository the repository of registered clients * @param authorizationService the authorization service * @param jwtEncoder the jwt encoder */ public OAuth2AuthorizationCodeAuthenticationProvider(RegisteredClientRepository registeredClientRepository, OAuth2AuthorizationService authorizationService, JwtEncoder jwtEncoder) { Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); Assert.notNull(authorizationService, "authorizationService cannot be null"); Assert.notNull(jwtEncoder, "jwtEncoder cannot be null"); this.registeredClientRepository = registeredClientRepository; this.authorizationService = authorizationService; this.jwtEncoder = jwtEncoder; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication; OAuth2ClientAuthenticationToken clientPrincipal = null; if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authorizationCodeAuthentication.getPrincipal().getClass())) { clientPrincipal = (OAuth2ClientAuthenticationToken) authorizationCodeAuthentication.getPrincipal(); } if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) { throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT)); } RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); OAuth2Authorization authorization = this.authorizationService.findByToken( authorizationCodeAuthentication.getCode(), TokenType.AUTHORIZATION_CODE); if (authorization == null) { throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)); } OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute( OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST); if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) { throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)); } if (StringUtils.hasText(authorizationRequest.getRedirectUri()) && !authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) { throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)); } JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build(); // TODO Allow configuration for issuer claim URL issuer = null; try { issuer = URI.create("https://oauth2.provider.com").toURL(); } catch (MalformedURLException e) { } Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token time-to-live Set authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES); JwtClaimsSet jwtClaimsSet = JwtClaimsSet.withClaims() .issuer(issuer) .subject(authorization.getPrincipalName()) .audience(Collections.singletonList(registeredClient.getClientId())) .issuedAt(issuedAt) .expiresAt(expiresAt) .notBefore(issuedAt) .claim(OAuth2ParameterNames.SCOPE, authorizedScopes) .build(); Jwt jwt = this.jwtEncoder.encode(joseHeader, jwtClaimsSet); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); authorization = OAuth2Authorization.from(authorization) .attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt) .tokens(OAuth2Tokens.builder().accessToken(accessToken).build()) .build(); this.authorizationService.save(authorization); return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken); } @Override public boolean supports(Class authentication) { return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication); } }