Generalize AuthorizationCodeAuthenticationProvider

The AuthorizationCodeAuthenticationProvider implements part of the
Authorization Code Grant flow as defined in
OAuth 2.0 Authorization Framework and OpenID Connect Core 1.0.
The implementation needs to be de-coupled to allow for better re-use and readability.
This commit introduces the AuthorizationGrantAuthenticator and extracts logic from
AuthorizationCodeAuthenticationProvider and provides different implementations
for OAuth 2.0 and OpenID Connect 1.0.

This re-factor is part of the work required for Issue gh-4513
This commit is contained in:
Joe Grandja 2017-10-04 17:32:02 -04:00
parent f184ada186
commit f8a9077d5a
8 changed files with 311 additions and 110 deletions

View File

@ -20,6 +20,9 @@ import org.springframework.security.config.annotation.web.configurers.AbstractAu
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticator;
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantAuthenticator;
import org.springframework.security.oauth2.client.authentication.DelegatingAuthorizationGrantAuthenticator;
import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry;
import org.springframework.security.oauth2.client.authentication.jwt.nimbus.NimbusJwtDecoderRegistry;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@ -34,6 +37,7 @@ import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExc
import org.springframework.security.oauth2.client.web.nimbus.NimbusAuthorizationCodeTokenExchanger;
import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizationCodeAuthenticator;
import org.springframework.security.oauth2.oidc.client.user.OidcUserService;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@ -51,6 +55,7 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
AbstractAuthenticationFilterConfigurer<H, AuthorizationCodeAuthenticationFilterConfigurer<H, R>, AuthorizationCodeAuthenticationProcessingFilter> {
private R authorizationResponseMatcher;
private AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> authorizationCodeAuthenticator;
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private SecurityTokenRepository<AccessToken> accessTokenRepository;
private JwtDecoderRegistry jwtDecoderRegistry;
@ -124,8 +129,7 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
@Override
public void init(H http) throws Exception {
AuthorizationCodeAuthenticationProvider authenticationProvider = new AuthorizationCodeAuthenticationProvider(
this.getAuthorizationCodeTokenExchanger(), this.getAccessTokenRepository(),
this.getJwtDecoderRegistry(), this.getUserInfoService());
this.getAuthorizationCodeAuthenticator(), this.getAccessTokenRepository(), this.getUserInfoService());
if (this.userAuthoritiesMapper != null) {
authenticationProvider.setAuthoritiesMapper(this.userAuthoritiesMapper);
}
@ -150,6 +154,17 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
this.authorizationResponseMatcher : this.getAuthenticationFilter().getAuthorizationResponseMatcher());
}
private AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> getAuthorizationCodeAuthenticator() {
if (this.authorizationCodeAuthenticator == null) {
List<AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken>> authenticators = new ArrayList<>();
authenticators.add(new AuthorizationCodeAuthenticator(this.getAuthorizationCodeTokenExchanger()));
authenticators.add(new OidcAuthorizationCodeAuthenticator(
this.getAuthorizationCodeTokenExchanger(), this.getJwtDecoderRegistry()));
this.authorizationCodeAuthenticator = new DelegatingAuthorizationGrantAuthenticator<>(authenticators);;
}
return this.authorizationCodeAuthenticator;
}
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> getAuthorizationCodeTokenExchanger() {
if (this.authorizationCodeTokenExchanger == null) {
this.authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger();

View File

@ -21,21 +21,12 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtDecoder;
import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.token.SecurityTokenRepository;
import org.springframework.security.oauth2.client.user.OAuth2UserService;
import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken;
import org.springframework.security.oauth2.oidc.client.authentication.OidcUserAuthenticationToken;
import org.springframework.security.oauth2.oidc.core.IdToken;
import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter;
import org.springframework.security.oauth2.oidc.core.user.OidcUser;
import org.springframework.util.Assert;
@ -51,16 +42,13 @@ import java.util.Collection;
* associating it with the returned {@link OAuth2UserAuthenticationToken}.
*
* <p>
* The {@link AuthorizationCodeAuthenticationProvider} uses an {@link AuthorizationGrantTokenExchanger}
* to make a request to the authorization server's <i>Token Endpoint</i>
* to verify the {@link AuthorizationCodeAuthenticationToken#getAuthorizationCode()}.
* If the request is valid, the authorization server will respond back with a {@link TokenResponseAttributes}.
* The {@link AuthorizationCodeAuthenticationProvider} uses an {@link AuthorizationGrantAuthenticator}
* to authenticate the {@link AuthorizationCodeAuthenticationToken#getAuthorizationCode()} and ultimately
* return an <i>&quot;Authorized Client&quot;</i> as an {@link OAuth2ClientAuthenticationToken}.
*
* <p>
* It will then create an {@link OAuth2ClientAuthenticationToken} associating the {@link AccessToken} and optionally
* the {@link IdToken} from the {@link TokenResponseAttributes} and pass it to
* {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken)} to obtain the end-user's (resource owner) attributes
* in the form of an {@link OAuth2User}.
* It will then call {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken)}
* to obtain the end-user's (resource owner) attributes in the form of an {@link OAuth2User}.
*
* <p>
* Finally, it will create an {@link OAuth2UserAuthenticationToken}, associating the {@link OAuth2User}
@ -73,10 +61,8 @@ import java.util.Collection;
* @see OAuth2ClientAuthenticationToken
* @see OidcClientAuthenticationToken
* @see OAuth2UserAuthenticationToken
* @see AuthorizationGrantTokenExchanger
* @see TokenResponseAttributes
* @see AccessToken
* @see IdToken
* @see OidcUserAuthenticationToken
* @see AuthorizationGrantAuthenticator
* @see OAuth2UserService
* @see OAuth2User
* @see OidcUser
@ -87,76 +73,51 @@ import java.util.Collection;
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 OpenID Connect Token Response</a>
*/
public class AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private final AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> authorizationCodeAuthenticator;
private final SecurityTokenRepository<AccessToken> accessTokenRepository;
private final JwtDecoderRegistry jwtDecoderRegistry;
private final OAuth2UserService userInfoService;
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
private final OAuth2UserService userService;
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public AuthorizationCodeAuthenticationProvider(
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> authorizationCodeAuthenticator,
SecurityTokenRepository<AccessToken> accessTokenRepository,
JwtDecoderRegistry jwtDecoderRegistry,
OAuth2UserService userInfoService) {
OAuth2UserService userService) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
Assert.notNull(authorizationCodeAuthenticator, "authorizationCodeAuthenticator cannot be null");
Assert.notNull(accessTokenRepository, "accessTokenRepository cannot be null");
Assert.notNull(jwtDecoderRegistry, "jwtDecoderRegistry cannot be null");
Assert.notNull(userInfoService, "userInfoService cannot be null");
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
Assert.notNull(userService, "userService cannot be null");
this.authorizationCodeAuthenticator = authorizationCodeAuthenticator;
this.accessTokenRepository = accessTokenRepository;
this.jwtDecoderRegistry = jwtDecoderRegistry;
this.userInfoService = userInfoService;
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(AuthorizationCodeAuthenticationToken) authentication;
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
TokenResponseAttributes tokenResponse =
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
OAuth2ClientAuthenticationToken oauth2ClientAuthentication =
this.authorizationCodeAuthenticator.authenticate(authorizationCodeAuthentication);
AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
tokenResponse.getExpiresAt(), tokenResponse.getScope());
this.accessTokenRepository.saveSecurityToken(
oauth2ClientAuthentication.getAccessToken(),
oauth2ClientAuthentication.getClientRegistration());
IdToken idToken = null;
if (tokenResponse.getAdditionalParameters().containsKey(OidcParameter.ID_TOKEN)) {
JwtDecoder jwtDecoder = this.jwtDecoderRegistry.getJwtDecoder(clientRegistration);
if (jwtDecoder == null) {
throw new IllegalArgumentException("Unable to find a registered JwtDecoder for Client Registration: '" + clientRegistration.getRegistrationId() +
"'. Check to ensure you have configured the JwkSet URI property.");
}
Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN));
idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
}
OAuth2User oauth2User = this.userService.loadUser(oauth2ClientAuthentication);
OAuth2ClientAuthenticationToken oauth2ClientAuthentication;
if (idToken != null) {
oauth2ClientAuthentication = new OidcClientAuthenticationToken(clientRegistration, accessToken, idToken);
} else {
oauth2ClientAuthentication = new OAuth2ClientAuthenticationToken(clientRegistration, accessToken);
}
oauth2ClientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
OAuth2User user = this.userInfoService.loadUser(oauth2ClientAuthentication);
Collection<? extends GrantedAuthority> authorities =
this.authoritiesMapper.mapAuthorities(user.getAuthorities());
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2UserAuthenticationToken oauth2UserAuthentication;
if (OidcUser.class.isAssignableFrom(user.getClass())) {
if (OidcUser.class.isAssignableFrom(oauth2User.getClass())) {
oauth2UserAuthentication = new OidcUserAuthenticationToken(
(OidcUser)user, authorities, (OidcClientAuthenticationToken)oauth2ClientAuthentication);
(OidcUser)oauth2User, mappedAuthorities, (OidcClientAuthenticationToken)oauth2ClientAuthentication);
} else {
oauth2UserAuthentication = new OAuth2UserAuthenticationToken(user, authorities, oauth2ClientAuthentication);
oauth2UserAuthentication = new OAuth2UserAuthenticationToken(
oauth2User, mappedAuthorities, oauth2ClientAuthentication);
}
oauth2UserAuthentication.setDetails(oauth2ClientAuthentication.getDetails());
this.accessTokenRepository.saveSecurityToken(accessToken, clientRegistration);
return oauth2UserAuthentication;
}

View File

@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
import org.springframework.util.Assert;
/**
@ -33,13 +34,18 @@ import org.springframework.util.Assert;
public class AuthorizationCodeAuthenticationToken extends AuthorizationGrantAuthenticationToken {
private final String authorizationCode;
private final ClientRegistration clientRegistration;
private final AuthorizationRequestAttributes authorizationRequest;
public AuthorizationCodeAuthenticationToken(String authorizationCode, ClientRegistration clientRegistration) {
public AuthorizationCodeAuthenticationToken(String authorizationCode,
ClientRegistration clientRegistration,
AuthorizationRequestAttributes authorizationRequest) {
super(AuthorizationGrantType.AUTHORIZATION_CODE, AuthorityUtils.NO_AUTHORITIES);
Assert.hasText(authorizationCode, "authorizationCode cannot be empty");
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
this.authorizationCode = authorizationCode;
this.clientRegistration = clientRegistration;
this.authorizationRequest = authorizationRequest;
this.setAuthenticated(false);
}
@ -60,4 +66,8 @@ public class AuthorizationCodeAuthenticationToken extends AuthorizationGrantAuth
public ClientRegistration getClientRegistration() {
return this.clientRegistration;
}
public AuthorizationRequestAttributes getAuthorizationRequest() {
return this.authorizationRequest;
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2017 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
*
* http://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.client.authentication;
import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
import org.springframework.util.Assert;
/**
* An implementation of an {@link AuthorizationGrantAuthenticator} that
* <i>&quot;authenticates&quot;</i> an <i>authorization code grant</i> credential
* against an OAuth 2.0 Provider's <i>Token Endpoint</i>.
*
* @author Joe Grandja
* @since 5.0
*/
public class AuthorizationCodeAuthenticator implements AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> {
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
public AuthorizationCodeAuthenticator(AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
}
@Override
public OAuth2ClientAuthenticationToken authenticate(
AuthorizationCodeAuthenticationToken authorizationCodeAuthentication) throws OAuth2AuthenticationException {
// Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
// If the openid scope value is not present, the behavior is entirely unspecified.
if (authorizationCodeAuthentication.getAuthorizationRequest().getScope().contains("openid")) {
// The OpenID Connect implementation of AuthorizationGrantAuthenticator
// must handle OpenID Connect Authentication Requests
return null;
}
TokenResponseAttributes tokenResponse =
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
tokenResponse.getExpiresAt(), tokenResponse.getScope());
OAuth2ClientAuthenticationToken oauth2ClientAuthentication =
new OAuth2ClientAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), accessToken);
oauth2ClientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
return oauth2ClientAuthentication;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2012-2017 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
*
* http://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.client.authentication;
/**
* A strategy used for <i>&quot;authenticating&quot;</i> an <i>authorization grant</i> credential
* with the authorization server's <i>Token Endpoint</i>.
*
* @author Joe Grandja
* @since 5.0
*/
public interface AuthorizationGrantAuthenticator<T extends AuthorizationGrantAuthenticationToken> {
OAuth2ClientAuthenticationToken authenticate(T authorizationGrantAuthentication) throws OAuth2AuthenticationException;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2012-2017 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
*
* http://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.client.authentication;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* An implementation of an {@link AuthorizationGrantAuthenticator} that
* simply delegates to one of the {@link AuthorizationGrantAuthenticator}'s that it composes.
*
* @author Joe Grandja
* @since 5.0
*/
public class DelegatingAuthorizationGrantAuthenticator<T extends AuthorizationGrantAuthenticationToken> implements AuthorizationGrantAuthenticator<T> {
private final Map<Class<? extends AuthorizationGrantAuthenticationToken>, List<AuthorizationGrantAuthenticator<T>>> authenticators = new HashMap<>();
public DelegatingAuthorizationGrantAuthenticator(List<AuthorizationGrantAuthenticator<T>> authenticators) {
Assert.notEmpty(authenticators, "authenticators cannot be empty");
authenticators.forEach(authenticator -> {
Class<? extends AuthorizationGrantAuthenticationToken> authenticationType =
ResolvableType.forInstance(authenticator).as(AuthorizationGrantAuthenticator.class)
.resolveGeneric(0).asSubclass(AuthorizationGrantAuthenticationToken.class);
this.authenticators
.computeIfAbsent(authenticationType, k -> new LinkedList<>())
.add(authenticator);
});
}
@Override
public OAuth2ClientAuthenticationToken authenticate(T authorizationGrantAuthentication) throws OAuth2AuthenticationException {
return this.authenticators.getOrDefault(authorizationGrantAuthentication.getClass(), Collections.emptyList())
.stream()
.map(authenticator -> authenticator.authenticate(authorizationGrantAuthentication))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
}

View File

@ -21,21 +21,15 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2UserAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.user.OAuth2UserService;
import org.springframework.security.oauth2.client.web.converter.AuthorizationCodeAuthorizationResponseAttributesConverter;
import org.springframework.security.oauth2.client.web.converter.ErrorResponseAttributesConverter;
import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.AuthorizationCodeAuthorizationResponseAttributes;
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
import org.springframework.security.oauth2.core.endpoint.ErrorResponseAttributes;
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@ -51,9 +45,9 @@ import java.io.IOException;
* the processing of an <i>OAuth 2.0 Authorization Response</i> for the authorization code grant flow.
*
* <p>
* This <code>Filter</code> processes the <i>Authorization Response</i> in the following step sequence:
* This <code>Filter</code> processes the <i>Authorization Response</i> as follows:
*
* <ol>
* <ul>
* <li>
* Assuming the resource owner (end-user) has granted access to the client, the authorization server will append the
* {@link OAuth2Parameter#CODE} and {@link OAuth2Parameter#STATE} (if provided in the <i>Authorization Request</i>) parameters
@ -62,47 +56,19 @@ import java.io.IOException;
* </li>
* <li>
* This <code>Filter</code> will then create an {@link AuthorizationCodeAuthenticationToken} with
* the {@link OAuth2Parameter#CODE} received in the previous step and pass it to
* the {@link OAuth2Parameter#CODE} received in the previous step and delegate it to
* {@link AuthorizationCodeAuthenticationProvider#authenticate(Authentication)} (indirectly via {@link AuthenticationManager}).
* The {@link AuthorizationCodeAuthenticationProvider} will use an {@link AuthorizationGrantTokenExchanger} to make a request
* to the authorization server's <i>Token Endpoint</i> for exchanging the {@link OAuth2Parameter#CODE} for an {@link AccessToken}.
* </li>
* <li>
* Upon receiving the <i>Access Token Request</i>, the authorization server will authenticate the client,
* verify the {@link OAuth2Parameter#CODE}, and ensure that the {@link OAuth2Parameter#REDIRECT_URI}
* received matches the <code>URI</code> originally provided in the <i>Authorization Request</i>.
* If the request is valid, the authorization server will respond back with a {@link TokenResponseAttributes}.
* </li>
* <li>
* The {@link AuthorizationCodeAuthenticationProvider} will then create a new {@link OAuth2ClientAuthenticationToken}
* associating the {@link AccessToken} from the {@link TokenResponseAttributes} and pass it to
* {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken)}. The {@link OAuth2UserService} will make a request
* to the authorization server's <i>UserInfo Endpoint</i> (using the {@link AccessToken})
* to obtain the end-user's (resource owner) attributes and return it in the form of an {@link OAuth2User}.
* </li>
* <li>
* The {@link AuthorizationCodeAuthenticationProvider} will then create a {@link OAuth2UserAuthenticationToken}
* associating the {@link OAuth2ClientAuthenticationToken} and {@link OAuth2User} returned from the {@link OAuth2UserService}.
* Finally, the {@link OAuth2UserAuthenticationToken} is returned to the {@link AuthenticationManager}
* and then back to this <code>Filter</code> at which point the session is considered <i>&quot;authenticated&quot;</i>.
* </li>
* </ol>
*
* <p>
* <b>NOTE:</b> Steps 4-5 are <b>not</b> part of the authorization code grant flow and instead are
* <i>&quot;authentication flow&quot;</i> steps that are required in order to authenticate the end-user with the system.
* </ul>
*
* @author Joe Grandja
* @since 5.0
* @see AbstractAuthenticationProcessingFilter
* @see AuthorizationCodeAuthenticationToken
* @see AuthorizationCodeAuthenticationProvider
* @see AuthorizationGrantTokenExchanger
* @see AuthorizationCodeAuthorizationResponseAttributes
* @see AuthorizationCodeRequestRedirectFilter
* @see AuthorizationRequestAttributes
* @see AuthorizationRequestRepository
* @see AuthorizationCodeRequestRedirectFilter
* @see ClientRegistration
* @see ClientRegistrationRepository
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
@ -154,7 +120,7 @@ public class AuthorizationCodeAuthenticationProcessingFilter extends AbstractAut
this.authorizationCodeResponseConverter.apply(request);
AuthorizationCodeAuthenticationToken authRequest = new AuthorizationCodeAuthenticationToken(
authorizationCodeResponseAttributes.getCode(), clientRegistration);
authorizationCodeResponseAttributes.getCode(), clientRegistration, matchingAuthorizationRequest);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));

View File

@ -0,0 +1,95 @@
/*
* Copyright 2012-2017 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
*
* http://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.oidc.client.authentication;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtDecoder;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantAuthenticator;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
import org.springframework.security.oauth2.oidc.core.IdToken;
import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter;
import org.springframework.util.Assert;
/**
* An implementation of an {@link AuthorizationGrantAuthenticator} that
* <i>&quot;authenticates&quot;</i> an <i>authorization code grant</i> credential
* against an OpenID Connect 1.0 Provider's <i>Token Endpoint</i>.
*
* @author Joe Grandja
* @since 5.0
*/
public class OidcAuthorizationCodeAuthenticator implements AuthorizationGrantAuthenticator<AuthorizationCodeAuthenticationToken> {
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private final JwtDecoderRegistry jwtDecoderRegistry;
public OidcAuthorizationCodeAuthenticator(
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
JwtDecoderRegistry jwtDecoderRegistry) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
Assert.notNull(jwtDecoderRegistry, "jwtDecoderRegistry cannot be null");
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
this.jwtDecoderRegistry = jwtDecoderRegistry;
}
@Override
public OAuth2ClientAuthenticationToken authenticate(
AuthorizationCodeAuthenticationToken authorizationCodeAuthentication) throws OAuth2AuthenticationException {
// Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
// If the openid scope value is not present, the behavior is entirely unspecified.
if (!authorizationCodeAuthentication.getAuthorizationRequest().getScope().contains("openid")) {
return null;
}
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
TokenResponseAttributes tokenResponse =
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
tokenResponse.getExpiresAt(), tokenResponse.getScope());
if (!tokenResponse.getAdditionalParameters().containsKey(OidcParameter.ID_TOKEN)) {
throw new IllegalArgumentException(
"Missing (required) ID Token in Token Response for Client Registration: '" + clientRegistration.getRegistrationId() + "'");
}
JwtDecoder jwtDecoder = this.jwtDecoderRegistry.getJwtDecoder(clientRegistration);
if (jwtDecoder == null) {
throw new IllegalArgumentException("Unable to find a registered JwtDecoder for Client Registration: '" + clientRegistration.getRegistrationId() +
"'. Check to ensure you have configured the JwkSet URI.");
}
Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN));
IdToken idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
OidcClientAuthenticationToken oidcClientAuthentication =
new OidcClientAuthenticationToken(clientRegistration, accessToken, idToken);
oidcClientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
return oidcClientAuthentication;
}
}