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:
parent
f184ada186
commit
f8a9077d5a
|
@ -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();
|
||||
|
|
|
@ -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>"Authorized Client"</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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>"authenticates"</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;
|
||||
}
|
||||
}
|
|
@ -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>"authenticating"</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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>"authenticated"</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>"authentication flow"</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));
|
||||
|
||||
|
|
|
@ -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>"authenticates"</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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue