Skip to content

Add OAuth2Authorization success/failure handlers #7986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-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.
Expand All @@ -19,6 +19,11 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientOAuth2AuthorizationFailureHandler;
import org.springframework.security.oauth2.client.web.SaveAuthorizedClientOAuth2AuthorizationSuccessHandler;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
Expand All @@ -31,20 +36,50 @@

/**
* An implementation of an {@link OAuth2AuthorizedClientManager}
* that is capable of operating outside of a {@code HttpServletRequest} context,
* that is capable of operating outside of the context of a {@code HttpServletRequest},
* e.g. in a scheduled/background thread and/or in the service-tier.
*
* <p>
* (When operating <em>within</em> the context of a {@code HttpServletRequest},
* use {@link DefaultOAuth2AuthorizedClientManager} instead.)
*
* <h2>Authorized Client Persistence</h2>
*
* <p>
* This manager utilizes an {@link OAuth2AuthorizedClientService}
* to persist {@link OAuth2AuthorizedClient}s.
*
* <p>
* By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
* will be saved in the {@link OAuth2AuthorizedClientService}.
* This functionality can be changed by configuring a custom {@link OAuth2AuthorizationSuccessHandler}
* via {@link #setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)}.
*
* <p>
* By default, when an authorization attempt fails due to an
* {@value OAuth2ErrorCodes#INVALID_GRANT} error,
* the previously saved {@link OAuth2AuthorizedClient}
* will be removed from the {@link OAuth2AuthorizedClientService}.
* (The {@value OAuth2ErrorCodes#INVALID_GRANT} error can occur
* when a refresh token that is no longer valid is used to retrieve a new access token.)
* This functionality can be changed by configuring a custom {@link OAuth2AuthorizationFailureHandler}
* via {@link #setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)}.
*
* @author Joe Grandja
* @since 5.2
* @see OAuth2AuthorizedClientManager
* @see OAuth2AuthorizedClientProvider
* @see OAuth2AuthorizedClientService
* @see OAuth2AuthorizationSuccessHandler
* @see OAuth2AuthorizationFailureHandler
*/
public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientService authorizedClientService;
private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper = new DefaultContextAttributesMapper();
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper;
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;

/**
* Constructs an {@code AuthorizedClientServiceOAuth2AuthorizedClientManager} using the provided parameters.
Expand All @@ -58,6 +93,9 @@ public AuthorizedClientServiceOAuth2AuthorizedClientManager(ClientRegistrationRe
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientService = authorizedClientService;
this.contextAttributesMapper = new DefaultContextAttributesMapper();
this.authorizationSuccessHandler = new SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(authorizedClientService);
this.authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(authorizedClientService);
}

@Nullable
Expand Down Expand Up @@ -92,9 +130,16 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest)
})
.build();

authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
try {
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
} catch (OAuth2AuthorizationException ex) {
this.authorizationFailureHandler.onAuthorizationFailure(ex, principal, Collections.emptyMap());
throw ex;
}

if (authorizedClient != null) {
this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
this.authorizationSuccessHandler.onAuthorizationSuccess(
authorizedClient, principal, Collections.emptyMap());
} else {
// In the case of re-authorization, the returned `authorizedClient` may be null if re-authorization is not supported.
// For these cases, return the provided `authorizationContext.authorizedClient`.
Expand Down Expand Up @@ -128,6 +173,36 @@ public void setContextAttributesMapper(Function<OAuth2AuthorizeRequest, Map<Stri
this.contextAttributesMapper = contextAttributesMapper;
}

/**
* Sets the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations.
*
* <p>
* A {@link SaveAuthorizedClientOAuth2AuthorizationSuccessHandler} is used by default.
*
* @param authorizationSuccessHandler the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations
* @see SaveAuthorizedClientOAuth2AuthorizationSuccessHandler
* @since 5.3
*/
public void setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
Assert.notNull(authorizationSuccessHandler, "authorizationSuccessHandler cannot be null");
this.authorizationSuccessHandler = authorizationSuccessHandler;
}

/**
* Sets the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures.
*
* <p>
* A {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} is used by default.
*
* @param authorizationFailureHandler the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures
* @see RemoveAuthorizedClientOAuth2AuthorizationFailureHandler
* @since 5.3
*/
public void setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
this.authorizationFailureHandler = authorizationFailureHandler;
}

/**
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2002-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.client;

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;

import java.util.Map;

/**
* Handles when an OAuth 2.0 Client fails to authorize (or re-authorize)
* via the Authorization Server or Resource Server.
*
* @author Joe Grandja
* @since 5.3
* @see OAuth2AuthorizedClient
* @see OAuth2AuthorizedClientManager
*/
@FunctionalInterface
public interface OAuth2AuthorizationFailureHandler {

/**
* Called when an OAuth 2.0 Client fails to authorize (or re-authorize)
* via the Authorization Server or Resource Server.
*
* @param authorizationException the exception that contains details about what failed
* @param principal the {@code Principal} associated with the attempted authorization
* @param attributes an immutable {@code Map} of (optional) attributes present under certain conditions.
* For example, this might contain a {@code javax.servlet.http.HttpServletRequest}
* and {@code javax.servlet.http.HttpServletResponse} if the authorization was performed
* within the context of a {@code javax.servlet.ServletContext}.
*/
void onAuthorizationFailure(OAuth2AuthorizationException authorizationException,
Authentication principal, Map<String, Object> attributes);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2002-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.client;

import org.springframework.security.core.Authentication;

import java.util.Map;

/**
* Handles when an OAuth 2.0 Client has been successfully
* authorized (or re-authorized) via the Authorization Server.
*
* @author Joe Grandja
* @since 5.3
* @see OAuth2AuthorizedClient
* @see OAuth2AuthorizedClientManager
*/
@FunctionalInterface
public interface OAuth2AuthorizationSuccessHandler {

/**
* Called when an OAuth 2.0 Client has been successfully
* authorized (or re-authorized) via the Authorization Server.
*
* @param authorizedClient the client that was successfully authorized (or re-authorized)
* @param principal the {@code Principal} associated with the authorized client
* @param attributes an immutable {@code Map} of (optional) attributes present under certain conditions.
* For example, this might contain a {@code javax.servlet.http.HttpServletRequest}
* and {@code javax.servlet.http.HttpServletResponse} if the authorization was performed
* within the context of a {@code javax.servlet.ServletContext}.
*/
void onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient,
Authentication principal, Map<String, Object> attributes);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-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.
Expand All @@ -20,16 +20,17 @@
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.oauth2.client.ClientAuthorizationException;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

Expand Down Expand Up @@ -74,9 +75,22 @@ public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRe
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
} catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
int statusCode = 500;
if (ex instanceof RestClientResponseException) {
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
}
OAuth2Error oauth2Error = new OAuth2Error(
INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
null);
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
statusCode,
oauth2Error);
throw new ClientAuthorizationException(
oauth2Error,
authorizationCodeGrantRequest.getClientRegistration().getRegistrationId(),
message,
ex);
}

OAuth2AccessTokenResponse tokenResponse = response.getBody();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-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.
Expand All @@ -20,16 +20,17 @@
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.oauth2.client.ClientAuthorizationException;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

Expand Down Expand Up @@ -74,9 +75,22 @@ public OAuth2AccessTokenResponse getTokenResponse(OAuth2ClientCredentialsGrantRe
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
} catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
int statusCode = 500;
if (ex instanceof RestClientResponseException) {
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
}
OAuth2Error oauth2Error = new OAuth2Error(
INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
null);
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
statusCode,
oauth2Error);
throw new ClientAuthorizationException(
oauth2Error,
clientCredentialsGrantRequest.getClientRegistration().getRegistrationId(),
message,
ex);
}

OAuth2AccessTokenResponse tokenResponse = response.getBody();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-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.
Expand All @@ -20,16 +20,17 @@
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.oauth2.client.ClientAuthorizationException;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

Expand Down Expand Up @@ -74,9 +75,22 @@ public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest pas
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
} catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
int statusCode = 500;
if (ex instanceof RestClientResponseException) {
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
}
OAuth2Error oauth2Error = new OAuth2Error(
INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
null);
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
statusCode,
oauth2Error);
throw new ClientAuthorizationException(
oauth2Error,
passwordGrantRequest.getClientRegistration().getRegistrationId(),
message,
ex);
}

OAuth2AccessTokenResponse tokenResponse = response.getBody();
Expand Down
Loading