Add support for OAuth 2.0 Login
Fixes gh-3907
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
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.*;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.springframework.security.oauth2.client.authentication.AuthorizationCodeRequestRedirectFilter.isDefaultRedirectUri;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractAuthenticationProcessingFilter} that handles
|
||||
* 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:
|
||||
*
|
||||
* <ol>
|
||||
* <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
|
||||
* to the {@link OAuth2Parameter#REDIRECT_URI} (provided in the <i>Authorization Request</i>)
|
||||
* and redirect the end-user's user-agent back to this <code>Filter</code> (the client).
|
||||
* </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
|
||||
* {@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 OAuth2AuthenticationToken}
|
||||
* associating the {@link AccessToken} from the {@link TokenResponseAttributes} and pass it to
|
||||
* {@link OAuth2UserService#loadUser(OAuth2AuthenticationToken)}. 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 create another new {@link OAuth2AuthenticationToken}
|
||||
* but this time associating the {@link AccessToken} and {@link OAuth2User} returned from the {@link OAuth2UserService}.
|
||||
* Finally, the {@link OAuth2AuthenticationToken} 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.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AbstractAuthenticationProcessingFilter
|
||||
* @see AuthorizationCodeAuthenticationToken
|
||||
* @see AuthorizationCodeAuthenticationProvider
|
||||
* @see AuthorizationGrantTokenExchanger
|
||||
* @see AuthorizationCodeAuthorizationResponseAttributes
|
||||
* @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>
|
||||
*/
|
||||
public class AuthorizationCodeAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
|
||||
public static final String AUTHORIZE_BASE_URI = "/oauth2/authorize/code";
|
||||
private static final String CLIENT_ALIAS_VARIABLE_NAME = "clientAlias";
|
||||
private static final String AUTHORIZE_URI = AUTHORIZE_BASE_URI + "/{" + CLIENT_ALIAS_VARIABLE_NAME + "}";
|
||||
private static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found";
|
||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
|
||||
private final ErrorResponseAttributesConverter errorResponseConverter = new ErrorResponseAttributesConverter();
|
||||
private final AuthorizationCodeAuthorizationResponseAttributesConverter authorizationCodeResponseConverter =
|
||||
new AuthorizationCodeAuthorizationResponseAttributesConverter();
|
||||
private final RequestMatcher authorizeRequestMatcher = new AntPathRequestMatcher(AUTHORIZE_URI);
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository();
|
||||
|
||||
public AuthorizationCodeAuthenticationProcessingFilter() {
|
||||
super(AUTHORIZE_URI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
ErrorResponseAttributes authorizationError = this.errorResponseConverter.convert(request);
|
||||
if (authorizationError != null) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(authorizationError.getErrorCode(),
|
||||
authorizationError.getDescription(), authorizationError.getUri());
|
||||
this.getAuthorizationRequestRepository().removeAuthorizationRequest(request);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
AuthorizationRequestAttributes matchingAuthorizationRequest = this.resolveAuthorizationRequest(request);
|
||||
|
||||
ClientRegistration clientRegistration = this.getClientRegistrationRepository().getRegistrationByClientId(
|
||||
matchingAuthorizationRequest.getClientId());
|
||||
|
||||
// If clientRegistration.redirectUri is the default one (with Uri template variables)
|
||||
// then use matchingAuthorizationRequest.redirectUri instead
|
||||
if (isDefaultRedirectUri(clientRegistration)) {
|
||||
clientRegistration = new ClientRegistrationBuilderWithUriOverrides(
|
||||
clientRegistration, matchingAuthorizationRequest.getRedirectUri()).build();
|
||||
}
|
||||
|
||||
AuthorizationCodeAuthorizationResponseAttributes authorizationCodeResponseAttributes =
|
||||
this.authorizationCodeResponseConverter.convert(request);
|
||||
|
||||
AuthorizationCodeAuthenticationToken authRequest = new AuthorizationCodeAuthenticationToken(
|
||||
authorizationCodeResponseAttributes.getCode(), clientRegistration);
|
||||
|
||||
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
|
||||
Authentication authenticated = this.getAuthenticationManager().authenticate(authRequest);
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
public RequestMatcher getAuthorizeRequestMatcher() {
|
||||
return this.authorizeRequestMatcher;
|
||||
}
|
||||
|
||||
protected ClientRegistrationRepository getClientRegistrationRepository() {
|
||||
return this.clientRegistrationRepository;
|
||||
}
|
||||
|
||||
public final void setClientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
Assert.notEmpty(clientRegistrationRepository.getRegistrations(), "clientRegistrationRepository cannot be empty");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
protected AuthorizationRequestRepository getAuthorizationRequestRepository() {
|
||||
return this.authorizationRequestRepository;
|
||||
}
|
||||
|
||||
public final void setAuthorizationRequestRepository(AuthorizationRequestRepository authorizationRequestRepository) {
|
||||
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
|
||||
this.authorizationRequestRepository = authorizationRequestRepository;
|
||||
}
|
||||
|
||||
private AuthorizationRequestAttributes resolveAuthorizationRequest(HttpServletRequest request) {
|
||||
AuthorizationRequestAttributes authorizationRequest =
|
||||
this.getAuthorizationRequestRepository().loadAuthorizationRequest(request);
|
||||
if (authorizationRequest == null) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
this.getAuthorizationRequestRepository().removeAuthorizationRequest(request);
|
||||
this.assertMatchingAuthorizationRequest(request, authorizationRequest);
|
||||
return authorizationRequest;
|
||||
}
|
||||
|
||||
private void assertMatchingAuthorizationRequest(HttpServletRequest request, AuthorizationRequestAttributes authorizationRequest) {
|
||||
String state = request.getParameter(OAuth2Parameter.STATE);
|
||||
if (!authorizationRequest.getState().equals(state)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
if (!request.getRequestURL().toString().equals(authorizationRequest.getRedirectUri())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClientRegistrationBuilderWithUriOverrides extends ClientRegistration.Builder {
|
||||
|
||||
private ClientRegistrationBuilderWithUriOverrides(ClientRegistration clientRegistration, String redirectUri) {
|
||||
super(clientRegistration.getClientId());
|
||||
this.clientSecret(clientRegistration.getClientSecret());
|
||||
this.clientAuthenticationMethod(clientRegistration.getClientAuthenticationMethod());
|
||||
this.authorizedGrantType(clientRegistration.getAuthorizedGrantType());
|
||||
this.redirectUri(redirectUri);
|
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
|
||||
this.scopes(clientRegistration.getScopes().stream().toArray(String[]::new));
|
||||
}
|
||||
this.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri());
|
||||
this.tokenUri(clientRegistration.getProviderDetails().getTokenUri());
|
||||
this.userInfoUri(clientRegistration.getProviderDetails().getUserInfoUri());
|
||||
this.clientName(clientRegistration.getClientName());
|
||||
this.clientAlias(clientRegistration.getClientAlias());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
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.oauth2.client.user.OAuth2UserService;
|
||||
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.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthenticationProvider} that is responsible for authenticating
|
||||
* an <i>authorization code</i> credential with the authorization server's <i>Token Endpoint</i>
|
||||
* and if valid, exchanging it for an <i>access token</i> credential.
|
||||
* Additionally, it will also obtain the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i>
|
||||
* (using the <i>access token</i>) and create a <code>Principal</code> in the form of an {@link OAuth2User}
|
||||
* associating it with the returned {@link OAuth2AuthenticationToken}.
|
||||
*
|
||||
* <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}.
|
||||
*
|
||||
* <p>
|
||||
* It will then create a {@link OAuth2AuthenticationToken} associating the {@link AccessToken}
|
||||
* from the {@link TokenResponseAttributes} and pass it to {@link OAuth2UserService#loadUser(OAuth2AuthenticationToken)}
|
||||
* to obtain the end-user's (resource owner) attributes in the form of an {@link OAuth2User}.
|
||||
*
|
||||
* <p>
|
||||
* Finally, it will create another {@link OAuth2AuthenticationToken}, this time associating
|
||||
* the {@link AccessToken} and {@link OAuth2User} and return it to the {@link AuthenticationManager},
|
||||
* at which point the {@link OAuth2AuthenticationToken} is considered <i>"authenticated"</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationCodeAuthenticationToken
|
||||
* @see AuthorizationGrantTokenExchanger
|
||||
* @see TokenResponseAttributes
|
||||
* @see AccessToken
|
||||
* @see OAuth2UserService
|
||||
* @see OAuth2User
|
||||
* @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.3">Section 4.1.3 Access Token Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||
*/
|
||||
public class AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
||||
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
|
||||
private final OAuth2UserService userInfoService;
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||
|
||||
public AuthorizationCodeAuthenticationProvider(
|
||||
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
|
||||
OAuth2UserService userInfoService) {
|
||||
|
||||
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
|
||||
Assert.notNull(userInfoService, "userInfoService cannot be null");
|
||||
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
|
||||
this.userInfoService = userInfoService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
||||
(AuthorizationCodeAuthenticationToken) authentication;
|
||||
|
||||
TokenResponseAttributes tokenResponse =
|
||||
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
|
||||
|
||||
AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
|
||||
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
|
||||
tokenResponse.getExpiresAt(), tokenResponse.getScopes());
|
||||
OAuth2AuthenticationToken accessTokenAuthentication = new OAuth2AuthenticationToken(
|
||||
authorizationCodeAuthentication.getClientRegistration(), accessToken);
|
||||
accessTokenAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
|
||||
|
||||
OAuth2User user = this.userInfoService.loadUser(accessTokenAuthentication);
|
||||
|
||||
Collection<? extends GrantedAuthority> authorities =
|
||||
this.authoritiesMapper.mapAuthorities(user.getAuthorities());
|
||||
|
||||
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(user, authorities,
|
||||
accessTokenAuthentication.getClientRegistration(), accessTokenAuthentication.getAccessToken());
|
||||
authenticationResult.setDetails(accessTokenAuthentication.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthorizationGrantAuthenticationToken} that holds
|
||||
* an <i>authorization code grant</i> credential for a specific client identified in {@link #getClientRegistration()}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationGrantAuthenticationToken
|
||||
* @see ClientRegistration
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3.1">Section 1.3.1 Authorization Code Grant</a>
|
||||
*/
|
||||
public class AuthorizationCodeAuthenticationToken extends AuthorizationGrantAuthenticationToken {
|
||||
private final String authorizationCode;
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
public AuthorizationCodeAuthenticationToken(String authorizationCode, ClientRegistration clientRegistration) {
|
||||
super(AuthorizationGrantType.AUTHORIZATION_CODE, AuthorityUtils.NO_AUTHORITIES);
|
||||
Assert.hasText(authorizationCode, "authorizationCode cannot be empty");
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
this.authorizationCode = authorizationCode;
|
||||
this.clientRegistration = clientRegistration;
|
||||
this.setAuthenticated(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.getAuthorizationCode();
|
||||
}
|
||||
|
||||
public String getAuthorizationCode() {
|
||||
return this.authorizationCode;
|
||||
}
|
||||
|
||||
public ClientRegistration getClientRegistration() {
|
||||
return this.clientRegistration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter.AUTHORIZE_BASE_URI;
|
||||
|
||||
/**
|
||||
* This <code>Filter</code> initiates the authorization code grant flow by redirecting
|
||||
* the end-user's user-agent to the authorization server's <i>Authorization Endpoint</i>.
|
||||
*
|
||||
* <p>
|
||||
* It uses an {@link AuthorizationRequestUriBuilder} to build the <i>OAuth 2.0 Authorization Request</i>,
|
||||
* which is used as the redirect <code>URI</code> to the <i>Authorization Endpoint</i>.
|
||||
* The redirect <code>URI</code> will include the client identifier, requested scope(s), state, response type, and a redirection URI
|
||||
* which the authorization server will send the user-agent back to (handled by {@link AuthorizationCodeAuthenticationProcessingFilter})
|
||||
* once access is granted (or denied) by the end-user (resource owner).
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationRequestAttributes
|
||||
* @see AuthorizationRequestRepository
|
||||
* @see AuthorizationRequestUriBuilder
|
||||
* @see ClientRegistration
|
||||
* @see ClientRegistrationRepository
|
||||
* @see AuthorizationCodeAuthenticationProcessingFilter
|
||||
* @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.1">Section 4.1.1 Authorization Request</a>
|
||||
*/
|
||||
public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter {
|
||||
public static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code";
|
||||
private static final String CLIENT_ALIAS_VARIABLE_NAME = "clientAlias";
|
||||
private static final String AUTHORIZATION_URI = AUTHORIZATION_BASE_URI + "/{" + CLIENT_ALIAS_VARIABLE_NAME + "}";
|
||||
private static final String DEFAULT_REDIRECT_URI_TEMPLATE = "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}";
|
||||
private final AntPathRequestMatcher authorizationRequestMatcher;
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
private final AuthorizationRequestUriBuilder authorizationUriBuilder;
|
||||
private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
|
||||
private final StringKeyGenerator stateGenerator = new DefaultStateGenerator();
|
||||
private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository();
|
||||
|
||||
public AuthorizationCodeRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository,
|
||||
AuthorizationRequestUriBuilder authorizationUriBuilder) {
|
||||
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
Assert.notNull(authorizationUriBuilder, "authorizationUriBuilder cannot be null");
|
||||
this.authorizationRequestMatcher = new AntPathRequestMatcher(AUTHORIZATION_URI);
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
this.authorizationUriBuilder = authorizationUriBuilder;
|
||||
}
|
||||
|
||||
public final void setAuthorizationRequestRepository(AuthorizationRequestRepository authorizationRequestRepository) {
|
||||
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
|
||||
this.authorizationRequestRepository = authorizationRequestRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (this.requiresAuthorization(request, response)) {
|
||||
try {
|
||||
this.sendRedirectForAuthorization(request, response);
|
||||
} catch (Exception failed) {
|
||||
this.unsuccessfulAuthorization(request, response, failed);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
protected boolean requiresAuthorization(HttpServletRequest request, HttpServletResponse response) {
|
||||
return this.authorizationRequestMatcher.matches(request);
|
||||
}
|
||||
|
||||
protected void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String clientAlias = this.authorizationRequestMatcher
|
||||
.extractUriTemplateVariables(request).get(CLIENT_ALIAS_VARIABLE_NAME);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.getRegistrationByClientAlias(clientAlias);
|
||||
if (clientRegistration == null) {
|
||||
throw new IllegalArgumentException("Invalid Client Identifier (Alias): " + clientAlias);
|
||||
}
|
||||
|
||||
String redirectUriStr;
|
||||
if (isDefaultRedirectUri(clientRegistration)) {
|
||||
redirectUriStr = this.expandDefaultRedirectUri(request, clientRegistration);
|
||||
} else {
|
||||
redirectUriStr = clientRegistration.getRedirectUri();
|
||||
}
|
||||
|
||||
AuthorizationRequestAttributes authorizationRequestAttributes =
|
||||
AuthorizationRequestAttributes.withAuthorizationCode()
|
||||
.clientId(clientRegistration.getClientId())
|
||||
.authorizeUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.redirectUri(redirectUriStr)
|
||||
.scopes(clientRegistration.getScopes())
|
||||
.state(this.stateGenerator.generateKey())
|
||||
.build();
|
||||
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequestAttributes, request);
|
||||
|
||||
URI redirectUri = this.authorizationUriBuilder.build(authorizationRequestAttributes);
|
||||
this.authorizationRedirectStrategy.sendRedirect(request, response, redirectUri.toString());
|
||||
}
|
||||
|
||||
protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response,
|
||||
Exception failed) throws IOException, ServletException {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Authorization Request failed: " + failed.toString(), failed);
|
||||
}
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, failed.getMessage());
|
||||
}
|
||||
|
||||
static boolean isDefaultRedirectUri(ClientRegistration clientRegistration) {
|
||||
return DEFAULT_REDIRECT_URI_TEMPLATE.equals(clientRegistration.getRedirectUri());
|
||||
}
|
||||
|
||||
private String expandDefaultRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) {
|
||||
return UriComponentsBuilder.fromUriString(DEFAULT_REDIRECT_URI_TEMPLATE)
|
||||
.buildAndExpand(request.getScheme(), request.getServerName(), request.getServerPort(),
|
||||
AUTHORIZE_BASE_URI, clientRegistration.getClientAlias())
|
||||
.encode()
|
||||
.toUriString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Base implementation of an {@link AbstractAuthenticationToken} that holds
|
||||
* an <i>authorization grant</i> credential for a specific {@link AuthorizationGrantType}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationGrantType
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
*/
|
||||
public abstract class AuthorizationGrantAuthenticationToken extends AbstractAuthenticationToken {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
private final AuthorizationGrantType authorizationGrantType;
|
||||
|
||||
protected AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
|
||||
super(authorities);
|
||||
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
|
||||
this.authorizationGrantType = authorizationGrantType;
|
||||
}
|
||||
|
||||
public AuthorizationGrantType getGrantType() {
|
||||
return this.authorizationGrantType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for <i>"exchanging"</i>
|
||||
* an <i>authorization grant</i> credential (for example, an authorization code) for an
|
||||
* <i>access token</i> credential at the authorization server's <i>Token Endpoint</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationGrantType
|
||||
* @see AuthorizationGrantAuthenticationToken
|
||||
* @see TokenResponseAttributes
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
*/
|
||||
public interface AuthorizationGrantTokenExchanger<T extends AuthorizationGrantAuthenticationToken> {
|
||||
|
||||
TokenResponseAttributes exchange(T authorizationGrantAuthentication) throws OAuth2AuthenticationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.core.endpoint.AuthorizationRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the persistence
|
||||
* of {@link AuthorizationRequestAttributes} between requests.
|
||||
*
|
||||
* <p>
|
||||
* Used by the {@link AuthorizationCodeRequestRedirectFilter} for persisting the <i>Authorization Request</i>
|
||||
* before it initiates the authorization code grant flow.
|
||||
* As well, used by the {@link AuthorizationCodeAuthenticationProcessingFilter} when resolving
|
||||
* the associated <i>Authorization Request</i> during the handling of the <i>Authorization Response</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationRequestAttributes
|
||||
* @see HttpSessionAuthorizationRequestRepository
|
||||
*/
|
||||
public interface AuthorizationRequestRepository {
|
||||
|
||||
AuthorizationRequestAttributes loadAuthorizationRequest(HttpServletRequest request);
|
||||
|
||||
void saveAuthorizationRequest(AuthorizationRequestAttributes authorizationRequest, HttpServletRequest request);
|
||||
|
||||
AuthorizationRequestAttributes removeAuthorizationRequest(HttpServletRequest request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.core.endpoint.AuthorizationRequestAttributes;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface AuthorizationRequestUriBuilder {
|
||||
|
||||
URI build(AuthorizationRequestAttributes authorizationRequestAttributes);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.core.endpoint.OAuth2Parameter;
|
||||
import org.springframework.security.oauth2.core.endpoint.ResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class DefaultAuthorizationRequestUriBuilder implements AuthorizationRequestUriBuilder {
|
||||
|
||||
@Override
|
||||
public URI build(AuthorizationRequestAttributes authorizationRequestAttributes) {
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||
.fromUriString(authorizationRequestAttributes.getAuthorizeUri())
|
||||
.queryParam(OAuth2Parameter.RESPONSE_TYPE, ResponseType.CODE.value());
|
||||
if (authorizationRequestAttributes.getRedirectUri() != null) {
|
||||
uriBuilder.queryParam(OAuth2Parameter.REDIRECT_URI, authorizationRequestAttributes.getRedirectUri());
|
||||
}
|
||||
uriBuilder
|
||||
.queryParam(OAuth2Parameter.CLIENT_ID, authorizationRequestAttributes.getClientId())
|
||||
.queryParam(OAuth2Parameter.SCOPE,
|
||||
authorizationRequestAttributes.getScopes().stream().collect(Collectors.joining(" ")))
|
||||
.queryParam(OAuth2Parameter.STATE, authorizationRequestAttributes.getState());
|
||||
|
||||
return uriBuilder.build().encode().toUri();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.crypto.codec.Base64;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The default implementation for generating the
|
||||
* {@link org.springframework.security.oauth2.core.endpoint.OAuth2Parameter#STATE} parameter
|
||||
* used in the <i>Authorization Request</i> and correlated in the <i>Authorization Response</i> (or <i>Error Response</i>).
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The value of the <i>state</i> parameter is an opaque <code>String</code>
|
||||
* used by the client to prevent cross-site request forgery, as described in
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-10.12">Section 10.12</a> of the specification.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class DefaultStateGenerator implements StringKeyGenerator {
|
||||
private static final int DEFAULT_BYTE_LENGTH = 32;
|
||||
private final BytesKeyGenerator keyGenerator;
|
||||
|
||||
public DefaultStateGenerator() {
|
||||
this(DEFAULT_BYTE_LENGTH);
|
||||
}
|
||||
|
||||
public DefaultStateGenerator(int byteLength) {
|
||||
Assert.isTrue(byteLength > 0, "byteLength must be greater than 0");
|
||||
this.keyGenerator = KeyGenerators.secureRandom(byteLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateKey() {
|
||||
return new String(Base64.encode(keyGenerator.generateKey()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.core.endpoint.AuthorizationRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthorizationRequestRepository} that stores
|
||||
* {@link AuthorizationRequestAttributes} in the {@link HttpSession}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationRequestAttributes
|
||||
*/
|
||||
public final class HttpSessionAuthorizationRequestRepository implements AuthorizationRequestRepository {
|
||||
private static final String DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME =
|
||||
HttpSessionAuthorizationRequestRepository.class.getName() + ".AUTHORIZATION_REQUEST";
|
||||
private String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME;
|
||||
|
||||
@Override
|
||||
public AuthorizationRequestAttributes loadAuthorizationRequest(HttpServletRequest request) {
|
||||
AuthorizationRequestAttributes authorizationRequest = null;
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
authorizationRequest = (AuthorizationRequestAttributes) session.getAttribute(this.sessionAttributeName);
|
||||
}
|
||||
return authorizationRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAuthorizationRequest(AuthorizationRequestAttributes authorizationRequest, HttpServletRequest request) {
|
||||
if (authorizationRequest == null) {
|
||||
this.removeAuthorizationRequest(request);
|
||||
return;
|
||||
}
|
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationRequestAttributes removeAuthorizationRequest(HttpServletRequest request) {
|
||||
AuthorizationRequestAttributes authorizationRequest = this.loadAuthorizationRequest(request);
|
||||
if (authorizationRequest != null) {
|
||||
request.getSession().removeAttribute(this.sessionAttributeName);
|
||||
}
|
||||
return 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.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This exception is thrown for all <i>OAuth 2.0</i> related {@link Authentication} errors.
|
||||
*
|
||||
* <p>
|
||||
* There are a number of scenarios where an error may occur, for example:
|
||||
* <ul>
|
||||
* <li>The authorization request or token request is missing a required parameter</li>
|
||||
* <li>Missing or invalid client identifier</li>
|
||||
* <li>Invalid or mismatching redirection URI</li>
|
||||
* <li>The requested scope is invalid, unknown, or malformed</li>
|
||||
* <li>The resource owner or authorization server denied the access request</li>
|
||||
* <li>Client authentication failed</li>
|
||||
* <li>The provided authorization grant (authorization code, resource owner credentials) is invalid, expired, or revoked</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class OAuth2AuthenticationException extends AuthenticationException {
|
||||
private OAuth2Error errorObject;
|
||||
|
||||
public OAuth2AuthenticationException(OAuth2Error errorObject, Throwable cause) {
|
||||
this(errorObject, cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
public OAuth2AuthenticationException(OAuth2Error errorObject, String message) {
|
||||
super(message);
|
||||
this.setErrorObject(errorObject);
|
||||
}
|
||||
|
||||
public OAuth2AuthenticationException(OAuth2Error errorObject, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.setErrorObject(errorObject);
|
||||
}
|
||||
|
||||
public OAuth2Error getErrorObject() {
|
||||
return errorObject;
|
||||
}
|
||||
|
||||
private void setErrorObject(OAuth2Error errorObject) {
|
||||
Assert.notNull(errorObject, "OAuth2 Error object cannot be null");
|
||||
this.errorObject = errorObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.AccessToken;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractAuthenticationToken}
|
||||
* that represents an <i>OAuth 2.0</i> {@link Authentication}.
|
||||
*
|
||||
* <p>
|
||||
* It associates an {@link OAuth2User}, {@link ClientRegistration} and an {@link AccessToken}.
|
||||
* This <code>Authentication</code> is considered <i>"authenticated"</i> if the {@link OAuth2User}
|
||||
* is provided in the respective constructor. This typically happens after the {@link OAuth2UserService}
|
||||
* retrieves the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2User
|
||||
* @see ClientRegistration
|
||||
* @see AccessToken
|
||||
*/
|
||||
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
private final OAuth2User principal;
|
||||
private final ClientRegistration clientRegistration;
|
||||
private final AccessToken accessToken;
|
||||
|
||||
public OAuth2AuthenticationToken(ClientRegistration clientRegistration, AccessToken accessToken) {
|
||||
this(null, AuthorityUtils.NO_AUTHORITIES, clientRegistration, accessToken);
|
||||
}
|
||||
|
||||
public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities,
|
||||
ClientRegistration clientRegistration, AccessToken accessToken) {
|
||||
|
||||
super(authorities);
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||
this.principal = principal;
|
||||
this.clientRegistration = clientRegistration;
|
||||
this.accessToken = accessToken;
|
||||
this.setAuthenticated(principal != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
// Credentials are never exposed (by the Provider) for an OAuth2 User
|
||||
return "";
|
||||
}
|
||||
|
||||
public ClientRegistration getClientRegistration() {
|
||||
return this.clientRegistration;
|
||||
}
|
||||
|
||||
public AccessToken getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.nimbus;
|
||||
|
||||
|
||||
import com.nimbusds.oauth2.sdk.*;
|
||||
import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
|
||||
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
|
||||
import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
|
||||
import com.nimbusds.oauth2.sdk.auth.Secret;
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
|
||||
import com.nimbusds.oauth2.sdk.id.ClientID;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AccessToken;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthorizationGrantTokenExchanger} that <i>"exchanges"</i>
|
||||
* an <i>authorization code</i> credential for an <i>access token</i> credential
|
||||
* at the authorization server's <i>Token Endpoint</i>.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationCodeAuthenticationToken
|
||||
* @see TokenResponseAttributes
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
*/
|
||||
public class NimbusAuthorizationCodeTokenExchanger implements AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> {
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
@Override
|
||||
public TokenResponseAttributes exchange(AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken)
|
||||
throws OAuth2AuthenticationException {
|
||||
|
||||
ClientRegistration clientRegistration = authorizationCodeAuthenticationToken.getClientRegistration();
|
||||
|
||||
// Build the authorization code grant request for the token endpoint
|
||||
AuthorizationCode authorizationCode = new AuthorizationCode(authorizationCodeAuthenticationToken.getAuthorizationCode());
|
||||
URI redirectUri = this.toURI(clientRegistration.getRedirectUri());
|
||||
AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri);
|
||||
URI tokenUri = this.toURI(clientRegistration.getProviderDetails().getTokenUri());
|
||||
|
||||
// Set the credentials to authenticate the client at the token endpoint
|
||||
ClientID clientId = new ClientID(clientRegistration.getClientId());
|
||||
Secret clientSecret = new Secret(clientRegistration.getClientSecret());
|
||||
ClientAuthentication clientAuthentication;
|
||||
if (ClientAuthenticationMethod.FORM.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
clientAuthentication = new ClientSecretPost(clientId, clientSecret);
|
||||
} else {
|
||||
clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
|
||||
}
|
||||
|
||||
TokenResponse tokenResponse;
|
||||
try {
|
||||
// Send the Access Token request
|
||||
TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant);
|
||||
HTTPRequest httpRequest = tokenRequest.toHTTPRequest();
|
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
tokenResponse = TokenResponse.parse(httpRequest.send());
|
||||
} catch (ParseException pe) {
|
||||
// This error occurs if the Access Token Response is not well-formed,
|
||||
// for example, a required attribute is missing
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE), pe);
|
||||
} catch (IOException ioe) {
|
||||
// This error occurs when there is a network-related issue
|
||||
throw new AuthenticationServiceException("An error occurred while sending the Access Token Request: " +
|
||||
ioe.getMessage(), ioe);
|
||||
}
|
||||
|
||||
if (!tokenResponse.indicatesSuccess()) {
|
||||
TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse;
|
||||
ErrorObject errorObject = tokenErrorResponse.getErrorObject();
|
||||
OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), errorObject.getDescription(),
|
||||
(errorObject.getURI() != null ? errorObject.getURI().toString() : null));
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
|
||||
|
||||
String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue();
|
||||
AccessToken.TokenType accessTokenType = null;
|
||||
if (AccessToken.TokenType.BEARER.value().equals(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) {
|
||||
accessTokenType = AccessToken.TokenType.BEARER;
|
||||
}
|
||||
long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime();
|
||||
Set<String> scopes = Collections.emptySet();
|
||||
if (!CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) {
|
||||
scopes = new HashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());
|
||||
}
|
||||
Map<String, Object> additionalParameters = accessTokenResponse.getCustomParameters().entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
return TokenResponseAttributes.withToken(accessToken)
|
||||
.tokenType(accessTokenType)
|
||||
.expiresIn(expiresIn)
|
||||
.scopes(scopes)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
}
|
||||
|
||||
private URI toURI(String uriStr) {
|
||||
try {
|
||||
return new URI(uriStr);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("An error occurred parsing URI: " + uriStr, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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.registration;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ClientRegistration {
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.HEADER;
|
||||
private AuthorizationGrantType authorizedGrantType;
|
||||
private String redirectUri;
|
||||
private Set<String> scopes = Collections.emptySet();
|
||||
private ProviderDetails providerDetails = new ProviderDetails();
|
||||
private String clientName;
|
||||
private String clientAlias;
|
||||
|
||||
protected ClientRegistration() {
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
protected void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
protected void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public ClientAuthenticationMethod getClientAuthenticationMethod() {
|
||||
return this.clientAuthenticationMethod;
|
||||
}
|
||||
|
||||
protected void setClientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) {
|
||||
this.clientAuthenticationMethod = clientAuthenticationMethod;
|
||||
}
|
||||
|
||||
public AuthorizationGrantType getAuthorizedGrantType() {
|
||||
return this.authorizedGrantType;
|
||||
}
|
||||
|
||||
protected void setAuthorizedGrantType(AuthorizationGrantType authorizedGrantType) {
|
||||
this.authorizedGrantType = authorizedGrantType;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return this.redirectUri;
|
||||
}
|
||||
|
||||
protected void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
protected void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public ProviderDetails getProviderDetails() {
|
||||
return this.providerDetails;
|
||||
}
|
||||
|
||||
protected void setProviderDetails(ProviderDetails providerDetails) {
|
||||
this.providerDetails = providerDetails;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return this.clientName;
|
||||
}
|
||||
|
||||
protected void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public String getClientAlias() {
|
||||
return this.clientAlias;
|
||||
}
|
||||
|
||||
protected void setClientAlias(String clientAlias) {
|
||||
this.clientAlias = clientAlias;
|
||||
}
|
||||
|
||||
public class ProviderDetails {
|
||||
private String authorizationUri;
|
||||
private String tokenUri;
|
||||
private String userInfoUri;
|
||||
|
||||
protected ProviderDetails() {
|
||||
}
|
||||
|
||||
public String getAuthorizationUri() {
|
||||
return this.authorizationUri;
|
||||
}
|
||||
|
||||
protected void setAuthorizationUri(String authorizationUri) {
|
||||
this.authorizationUri = authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenUri() {
|
||||
return this.tokenUri;
|
||||
}
|
||||
|
||||
protected void setTokenUri(String tokenUri) {
|
||||
this.tokenUri = tokenUri;
|
||||
}
|
||||
|
||||
public String getUserInfoUri() {
|
||||
return this.userInfoUri;
|
||||
}
|
||||
|
||||
protected void setUserInfoUri(String userInfoUri) {
|
||||
this.userInfoUri = userInfoUri;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
protected String clientId;
|
||||
protected String clientSecret;
|
||||
protected ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.HEADER;
|
||||
protected AuthorizationGrantType authorizedGrantType;
|
||||
protected String redirectUri;
|
||||
protected Set<String> scopes;
|
||||
protected String authorizationUri;
|
||||
protected String tokenUri;
|
||||
protected String userInfoUri;
|
||||
protected String clientName;
|
||||
protected String clientAlias;
|
||||
|
||||
public Builder(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public Builder(ClientRegistrationProperties clientRegistrationProperties) {
|
||||
this(clientRegistrationProperties.getClientId());
|
||||
this.clientSecret(clientRegistrationProperties.getClientSecret());
|
||||
this.clientAuthenticationMethod(clientRegistrationProperties.getClientAuthenticationMethod());
|
||||
this.authorizedGrantType(clientRegistrationProperties.getAuthorizedGrantType());
|
||||
this.redirectUri(clientRegistrationProperties.getRedirectUri());
|
||||
if (!CollectionUtils.isEmpty(clientRegistrationProperties.getScopes())) {
|
||||
this.scopes(clientRegistrationProperties.getScopes().stream().toArray(String[]::new));
|
||||
}
|
||||
this.authorizationUri(clientRegistrationProperties.getAuthorizationUri());
|
||||
this.tokenUri(clientRegistrationProperties.getTokenUri());
|
||||
this.userInfoUri(clientRegistrationProperties.getUserInfoUri());
|
||||
this.clientName(clientRegistrationProperties.getClientName());
|
||||
this.clientAlias(clientRegistrationProperties.getClientAlias());
|
||||
}
|
||||
|
||||
public Builder clientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) {
|
||||
this.clientAuthenticationMethod = clientAuthenticationMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder authorizedGrantType(AuthorizationGrantType authorizedGrantType) {
|
||||
this.authorizedGrantType = authorizedGrantType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder redirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder scopes(String... scopes) {
|
||||
if (scopes != null && scopes.length > 0) {
|
||||
this.scopes = Collections.unmodifiableSet(
|
||||
new LinkedHashSet<>(Arrays.asList(scopes)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder authorizationUri(String authorizationUri) {
|
||||
this.authorizationUri = authorizationUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tokenUri(String tokenUri) {
|
||||
this.tokenUri = tokenUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder userInfoUri(String userInfoUri) {
|
||||
this.userInfoUri = userInfoUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clientAlias(String clientAlias) {
|
||||
this.clientAlias = clientAlias;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistration build() {
|
||||
this.validateClientWithAuthorizationCodeGrantType();
|
||||
ClientRegistration clientRegistration = new ClientRegistration();
|
||||
this.setProperties(clientRegistration);
|
||||
return clientRegistration;
|
||||
}
|
||||
|
||||
protected void setProperties(ClientRegistration clientRegistration) {
|
||||
clientRegistration.setClientId(this.clientId);
|
||||
clientRegistration.setClientSecret(this.clientSecret);
|
||||
clientRegistration.setClientAuthenticationMethod(this.clientAuthenticationMethod);
|
||||
clientRegistration.setAuthorizedGrantType(this.authorizedGrantType);
|
||||
clientRegistration.setRedirectUri(this.redirectUri);
|
||||
clientRegistration.setScopes(this.scopes);
|
||||
|
||||
ProviderDetails providerDetails = clientRegistration.new ProviderDetails();
|
||||
providerDetails.setAuthorizationUri(this.authorizationUri);
|
||||
providerDetails.setTokenUri(this.tokenUri);
|
||||
providerDetails.setUserInfoUri(this.userInfoUri);
|
||||
clientRegistration.setProviderDetails(providerDetails);
|
||||
|
||||
clientRegistration.setClientName(this.clientName);
|
||||
clientRegistration.setClientAlias(this.clientAlias);
|
||||
}
|
||||
|
||||
protected void validateClientWithAuthorizationCodeGrantType() {
|
||||
Assert.isTrue(AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizedGrantType),
|
||||
"authorizedGrantType must be " + AuthorizationGrantType.AUTHORIZATION_CODE.value());
|
||||
Assert.hasText(this.clientId, "clientId cannot be empty");
|
||||
Assert.hasText(this.clientSecret, "clientSecret cannot be empty");
|
||||
Assert.notNull(this.clientAuthenticationMethod, "clientAuthenticationMethod cannot be null");
|
||||
Assert.hasText(this.redirectUri, "redirectUri cannot be empty");
|
||||
Assert.notEmpty(this.scopes, "scopes cannot be empty");
|
||||
Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty");
|
||||
Assert.hasText(this.tokenUri, "tokenUri cannot be empty");
|
||||
Assert.hasText(this.userInfoUri, "userInfoUri cannot be empty");
|
||||
Assert.hasText(this.clientName, "clientName cannot be empty");
|
||||
Assert.hasText(this.clientAlias, "clientAlias cannot be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.registration;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ClientRegistrationProperties {
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.HEADER;
|
||||
private AuthorizationGrantType authorizedGrantType;
|
||||
private String redirectUri;
|
||||
private Set<String> scopes;
|
||||
private String authorizationUri;
|
||||
private String tokenUri;
|
||||
private String userInfoUri;
|
||||
private String clientName;
|
||||
private String clientAlias;
|
||||
|
||||
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public ClientAuthenticationMethod getClientAuthenticationMethod() {
|
||||
return this.clientAuthenticationMethod;
|
||||
}
|
||||
|
||||
public void setClientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) {
|
||||
this.clientAuthenticationMethod = clientAuthenticationMethod;
|
||||
}
|
||||
|
||||
public AuthorizationGrantType getAuthorizedGrantType() {
|
||||
return this.authorizedGrantType;
|
||||
}
|
||||
|
||||
public void setAuthorizedGrantType(AuthorizationGrantType authorizedGrantType) {
|
||||
this.authorizedGrantType = authorizedGrantType;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return this.redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public String getAuthorizationUri() {
|
||||
return this.authorizationUri;
|
||||
}
|
||||
|
||||
public void setAuthorizationUri(String authorizationUri) {
|
||||
this.authorizationUri = authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenUri() {
|
||||
return this.tokenUri;
|
||||
}
|
||||
|
||||
public void setTokenUri(String tokenUri) {
|
||||
this.tokenUri = tokenUri;
|
||||
}
|
||||
|
||||
public String getUserInfoUri() {
|
||||
return this.userInfoUri;
|
||||
}
|
||||
|
||||
public void setUserInfoUri(String userInfoUri) {
|
||||
this.userInfoUri = userInfoUri;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return this.clientName;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public String getClientAlias() {
|
||||
return this.clientAlias;
|
||||
}
|
||||
|
||||
public void setClientAlias(String clientAlias) {
|
||||
this.clientAlias = clientAlias;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.registration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ClientRegistrationRepository {
|
||||
|
||||
ClientRegistration getRegistrationByClientId(String clientId);
|
||||
|
||||
ClientRegistration getRegistrationByClientAlias(String clientAlias);
|
||||
|
||||
List<ClientRegistration> getRegistrations();
|
||||
|
||||
}
|
||||
@@ -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.registration;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public final class InMemoryClientRegistrationRepository implements ClientRegistrationRepository {
|
||||
private final List<ClientRegistration> clientRegistrations;
|
||||
|
||||
public InMemoryClientRegistrationRepository(List<ClientRegistration> clientRegistrations) {
|
||||
Assert.notEmpty(clientRegistrations, "clientRegistrations cannot be empty");
|
||||
this.clientRegistrations = Collections.unmodifiableList(clientRegistrations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration getRegistrationByClientId(String clientId) {
|
||||
Optional<ClientRegistration> clientRegistration =
|
||||
this.clientRegistrations.stream()
|
||||
.filter(c -> c.getClientId().equals(clientId))
|
||||
.findFirst();
|
||||
return clientRegistration.isPresent() ? clientRegistration.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration getRegistrationByClientAlias(String clientAlias) {
|
||||
Optional<ClientRegistration> clientRegistration =
|
||||
this.clientRegistrations.stream()
|
||||
.filter(c -> c.getClientAlias().equals(clientAlias))
|
||||
.findFirst();
|
||||
return clientRegistration.isPresent() ? clientRegistration.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientRegistration> getRegistrations() {
|
||||
return this.clientRegistrations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.user;
|
||||
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.oidc.user.UserInfo;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for obtaining
|
||||
* the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i>
|
||||
* using the provided {@link OAuth2AuthenticationToken#getAccessToken()}
|
||||
* and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}
|
||||
* (for a standard <i>OAuth 2.0 Provider</i>) or {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>).
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2AuthenticationToken
|
||||
* @see AuthenticatedPrincipal
|
||||
* @see OAuth2User
|
||||
* @see UserInfo
|
||||
*/
|
||||
public interface OAuth2UserService {
|
||||
|
||||
OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.user.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract class AbstractOAuth2UserConverter<T extends OAuth2User> implements Converter<ClientHttpResponse, T> {
|
||||
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
|
||||
protected AbstractOAuth2UserConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T convert(ClientHttpResponse clientHttpResponse) {
|
||||
Map<String, Object> userAttributes;
|
||||
|
||||
try {
|
||||
userAttributes = (Map<String, Object>) this.jackson2HttpMessageConverter.read(Map.class, clientHttpResponse);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException("An error occurred reading the UserInfo response: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return this.convert(userAttributes);
|
||||
}
|
||||
|
||||
protected abstract T convert(Map<String, Object> userAttributes);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.user.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public final class CustomOAuth2UserConverter<T extends OAuth2User> implements Converter<ClientHttpResponse, T> {
|
||||
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
private final Class<T> customType;
|
||||
|
||||
public CustomOAuth2UserConverter(Class<T> customType) {
|
||||
this.customType = customType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(ClientHttpResponse clientHttpResponse) {
|
||||
T user;
|
||||
|
||||
try {
|
||||
user = (T) this.jackson2HttpMessageConverter.read(this.customType, clientHttpResponse);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException("An error occurred reading the UserInfo response: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.user.converter;
|
||||
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public final class OAuth2UserConverter extends AbstractOAuth2UserConverter<OAuth2User> {
|
||||
private final String nameAttributeKey;
|
||||
|
||||
public OAuth2UserConverter(String nameAttributeKey) {
|
||||
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
|
||||
this.nameAttributeKey = nameAttributeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OAuth2User convert(Map<String, Object> userAttributes) {
|
||||
return new DefaultOAuth2User(userAttributes, this.nameAttributeKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.user.converter;
|
||||
|
||||
import org.springframework.security.oauth2.oidc.user.DefaultUserInfo;
|
||||
import org.springframework.security.oauth2.oidc.user.UserInfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public final class UserInfoConverter extends AbstractOAuth2UserConverter<UserInfo> {
|
||||
|
||||
@Override
|
||||
protected UserInfo convert(Map<String, Object> userAttributes) {
|
||||
return new DefaultUserInfo(userAttributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.user.nimbus;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.AbstractClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
final class NimbusClientHttpResponse extends AbstractClientHttpResponse {
|
||||
private final HTTPResponse httpResponse;
|
||||
private final HttpHeaders headers;
|
||||
|
||||
NimbusClientHttpResponse(HTTPResponse httpResponse) {
|
||||
Assert.notNull(httpResponse, "httpResponse cannot be null");
|
||||
this.httpResponse = httpResponse;
|
||||
this.headers = new HttpHeaders();
|
||||
this.headers.setAll(httpResponse.getHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() throws IOException {
|
||||
return this.httpResponse.getStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusText() throws IOException {
|
||||
return String.valueOf(this.getRawStatusCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getBody() throws IOException {
|
||||
InputStream inputStream = new ByteArrayInputStream(
|
||||
this.httpResponse.getContent().getBytes(Charset.forName("UTF-8")));
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.user.nimbus;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.ErrorObject;
|
||||
import com.nimbusds.oauth2.sdk.ParseException;
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
|
||||
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoRequest;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.oidc.user.UserInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2UserService} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
|
||||
*
|
||||
* <p>
|
||||
* This implementation uses a <code>Map</code> of <code>Converter</code>'s <i>keyed</i> by <code>URI</code>.
|
||||
* The <code>URI</code> represents the <i>UserInfo Endpoint</i> address and the mapped <code>Converter</code>
|
||||
* is capable of converting the <i>UserInfo Response</i> to either an
|
||||
* {@link OAuth2User} (for a standard <i>OAuth 2.0 Provider</i>) or
|
||||
* {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>).
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2AuthenticationToken
|
||||
* @see AuthenticatedPrincipal
|
||||
* @see OAuth2User
|
||||
* @see UserInfo
|
||||
* @see Converter
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
|
||||
*/
|
||||
public class NimbusOAuth2UserService implements OAuth2UserService {
|
||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
||||
private final Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters;
|
||||
|
||||
public NimbusOAuth2UserService(Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters) {
|
||||
Assert.notEmpty(userInfoTypeConverters, "userInfoTypeConverters cannot be empty");
|
||||
this.userInfoTypeConverters = new HashMap<>(userInfoTypeConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException {
|
||||
OAuth2User user;
|
||||
|
||||
try {
|
||||
ClientRegistration clientRegistration = token.getClientRegistration();
|
||||
|
||||
URI userInfoUri;
|
||||
try {
|
||||
userInfoUri = new URI(clientRegistration.getProviderDetails().getUserInfoUri());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("An error occurred parsing the userInfo URI: " +
|
||||
clientRegistration.getProviderDetails().getUserInfoUri(), ex);
|
||||
}
|
||||
|
||||
Converter<ClientHttpResponse, ? extends OAuth2User> userInfoConverter = this.userInfoTypeConverters.get(userInfoUri);
|
||||
if (userInfoConverter == null) {
|
||||
throw new IllegalArgumentException("There is no available User Info converter for " + userInfoUri.toString());
|
||||
}
|
||||
|
||||
BearerAccessToken accessToken = new BearerAccessToken(token.getAccessToken().getTokenValue());
|
||||
|
||||
// Request the User Info
|
||||
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
|
||||
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
|
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
HTTPResponse httpResponse = httpRequest.send();
|
||||
|
||||
if (httpResponse.getStatusCode() != HTTPResponse.SC_OK) {
|
||||
UserInfoErrorResponse userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse);
|
||||
ErrorObject errorObject = userInfoErrorResponse.getErrorObject();
|
||||
OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), errorObject.getDescription(),
|
||||
(errorObject.getURI() != null ? errorObject.getURI().toString() : null));
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
user = userInfoConverter.convert(new NimbusClientHttpResponse(httpResponse));
|
||||
|
||||
} catch (ParseException ex) {
|
||||
// This error occurs if the User Info Response is not well-formed or invalid
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE), ex);
|
||||
} catch (IOException ex) {
|
||||
// This error occurs when there is a network-related issue
|
||||
throw new AuthenticationServiceException("An error occurred while sending the User Info Request: " +
|
||||
ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.web.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationCodeAuthorizationResponseAttributes;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public final class AuthorizationCodeAuthorizationResponseAttributesConverter implements Converter<HttpServletRequest, AuthorizationCodeAuthorizationResponseAttributes> {
|
||||
|
||||
@Override
|
||||
public AuthorizationCodeAuthorizationResponseAttributes convert(HttpServletRequest request) {
|
||||
AuthorizationCodeAuthorizationResponseAttributes response;
|
||||
|
||||
String code = request.getParameter(OAuth2Parameter.CODE);
|
||||
Assert.hasText(code, OAuth2Parameter.CODE + " attribute is required");
|
||||
|
||||
String state = request.getParameter(OAuth2Parameter.STATE);
|
||||
|
||||
response = new AuthorizationCodeAuthorizationResponseAttributes(code, state);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.web.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.core.endpoint.ErrorResponseAttributes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public final class ErrorResponseAttributesConverter implements Converter<HttpServletRequest, ErrorResponseAttributes> {
|
||||
|
||||
@Override
|
||||
public ErrorResponseAttributes convert(HttpServletRequest request) {
|
||||
ErrorResponseAttributes response;
|
||||
|
||||
String errorCode = request.getParameter(OAuth2Parameter.ERROR);
|
||||
if (!StringUtils.hasText(errorCode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String description = request.getParameter(OAuth2Parameter.ERROR_DESCRIPTION);
|
||||
String uri = request.getParameter(OAuth2Parameter.ERROR_URI);
|
||||
String state = request.getParameter(OAuth2Parameter.STATE);
|
||||
|
||||
response = ErrorResponseAttributes.withErrorCode(errorCode)
|
||||
.description(description)
|
||||
.uri(uri)
|
||||
.state(state)
|
||||
.build();
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.security.oauth2.client.authentication.TestUtil.*;
|
||||
|
||||
/**
|
||||
* Tests {@link AuthorizationCodeAuthenticationProcessingFilter}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class AuthorizationCodeAuthenticationProcessingFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotAuthorizationCodeResponseThenContinueChain() throws Exception {
|
||||
ClientRegistration clientRegistration = googleClientRegistration();
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration));
|
||||
|
||||
String requestURI = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI);
|
||||
request.setServletPath(requestURI);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
verify(filter, never()).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationCodeErrorResponseThenAuthenticationFailureHandlerIsCalled() throws Exception {
|
||||
ClientRegistration clientRegistration = githubClientRegistration();
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration));
|
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class);
|
||||
filter.setAuthenticationFailureHandler(failureHandler);
|
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration);
|
||||
String errorCode = OAuth2Error.INVALID_GRANT_ERROR_CODE;
|
||||
request.addParameter(OAuth2Parameter.ERROR, errorCode);
|
||||
request.addParameter(OAuth2Parameter.STATE, "some state");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationCodeSuccessResponseThenAuthenticationSuccessHandlerIsCalled() throws Exception {
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("joe", "password", "user", "admin");
|
||||
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
|
||||
when(authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication);
|
||||
|
||||
ClientRegistration clientRegistration = githubClientRegistration();
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(authenticationManager, clientRegistration));
|
||||
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class);
|
||||
filter.setAuthenticationSuccessHandler(successHandler);
|
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository();
|
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository);
|
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration);
|
||||
String authCode = "some code";
|
||||
String state = "some state";
|
||||
request.addParameter(OAuth2Parameter.CODE, authCode);
|
||||
request.addParameter(OAuth2Parameter.STATE, state);
|
||||
setupAuthorizationRequest(authorizationRequestRepository, request, clientRegistration, state);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
|
||||
ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class);
|
||||
verify(successHandler).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
authenticationArgCaptor.capture());
|
||||
assertThat(authenticationArgCaptor.getValue()).isEqualTo(authentication);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationCodeSuccessResponseAndNoMatchingAuthorizationRequestThenThrowOAuth2AuthenticationExceptionAuthorizationRequestNotFound() throws Exception {
|
||||
ClientRegistration clientRegistration = githubClientRegistration();
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration));
|
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class);
|
||||
filter.setAuthenticationFailureHandler(failureHandler);
|
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration);
|
||||
String authCode = "some code";
|
||||
String state = "some state";
|
||||
request.addParameter(OAuth2Parameter.CODE, authCode);
|
||||
request.addParameter(OAuth2Parameter.STATE, state);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(filter, failureHandler, "authorization_request_not_found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationCodeSuccessResponseWithInvalidStateParamThenThrowOAuth2AuthenticationExceptionInvalidStateParameter() throws Exception {
|
||||
ClientRegistration clientRegistration = githubClientRegistration();
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration));
|
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class);
|
||||
filter.setAuthenticationFailureHandler(failureHandler);
|
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository();
|
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository);
|
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration);
|
||||
String authCode = "some code";
|
||||
String state = "some other state";
|
||||
request.addParameter(OAuth2Parameter.CODE, authCode);
|
||||
request.addParameter(OAuth2Parameter.STATE, state);
|
||||
setupAuthorizationRequest(authorizationRequestRepository, request, clientRegistration, "some state");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(filter, failureHandler, "invalid_state_parameter");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationCodeSuccessResponseWithInvalidRedirectUriParamThenThrowOAuth2AuthenticationExceptionInvalidRedirectUriParameter() throws Exception {
|
||||
ClientRegistration clientRegistration = githubClientRegistration();
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration));
|
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class);
|
||||
filter.setAuthenticationFailureHandler(failureHandler);
|
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository();
|
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository);
|
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration);
|
||||
request.setRequestURI(request.getRequestURI() + "-other");
|
||||
String authCode = "some code";
|
||||
String state = "some state";
|
||||
request.addParameter(OAuth2Parameter.CODE, authCode);
|
||||
request.addParameter(OAuth2Parameter.STATE, state);
|
||||
setupAuthorizationRequest(authorizationRequestRepository, request, clientRegistration, state);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(filter, failureHandler, "invalid_redirect_uri_parameter");
|
||||
}
|
||||
|
||||
private void verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(AuthorizationCodeAuthenticationProcessingFilter filter,
|
||||
AuthenticationFailureHandler failureHandler,
|
||||
String errorCode) throws Exception {
|
||||
|
||||
verify(filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
|
||||
ArgumentCaptor<AuthenticationException> authenticationExceptionArgCaptor =
|
||||
ArgumentCaptor.forClass(AuthenticationException.class);
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
authenticationExceptionArgCaptor.capture());
|
||||
assertThat(authenticationExceptionArgCaptor.getValue()).isInstanceOf(OAuth2AuthenticationException.class);
|
||||
OAuth2AuthenticationException oauth2AuthenticationException =
|
||||
(OAuth2AuthenticationException)authenticationExceptionArgCaptor.getValue();
|
||||
assertThat(oauth2AuthenticationException.getErrorObject()).isNotNull();
|
||||
assertThat(oauth2AuthenticationException.getErrorObject().getErrorCode()).isEqualTo(errorCode);
|
||||
}
|
||||
|
||||
private AuthorizationCodeAuthenticationProcessingFilter setupFilter(ClientRegistration... clientRegistrations) throws Exception {
|
||||
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
|
||||
|
||||
return setupFilter(authenticationManager, clientRegistrations);
|
||||
}
|
||||
|
||||
private AuthorizationCodeAuthenticationProcessingFilter setupFilter(
|
||||
AuthenticationManager authenticationManager, ClientRegistration... clientRegistrations) throws Exception {
|
||||
|
||||
ClientRegistrationRepository clientRegistrationRepository = clientRegistrationRepository(clientRegistrations);
|
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = new AuthorizationCodeAuthenticationProcessingFilter();
|
||||
filter.setClientRegistrationRepository(clientRegistrationRepository);
|
||||
filter.setAuthenticationManager(authenticationManager);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private void setupAuthorizationRequest(AuthorizationRequestRepository authorizationRequestRepository,
|
||||
HttpServletRequest request,
|
||||
ClientRegistration clientRegistration,
|
||||
String state) {
|
||||
|
||||
AuthorizationRequestAttributes authorizationRequestAttributes =
|
||||
AuthorizationRequestAttributes.withAuthorizationCode()
|
||||
.clientId(clientRegistration.getClientId())
|
||||
.authorizeUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.redirectUri(clientRegistration.getRedirectUri())
|
||||
.scopes(clientRegistration.getScopes())
|
||||
.state(state)
|
||||
.build();
|
||||
|
||||
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequestAttributes, request);
|
||||
}
|
||||
|
||||
private MockHttpServletRequest setupRequest(ClientRegistration clientRegistration) {
|
||||
String requestURI = AUTHORIZE_BASE_URI + "/" + clientRegistration.getClientAlias();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI);
|
||||
request.setScheme(DEFAULT_SCHEME);
|
||||
request.setServerName(DEFAULT_SERVER_NAME);
|
||||
request.setServerPort(DEFAULT_SERVER_PORT);
|
||||
request.setServletPath(requestURI);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.security.oauth2.client.authentication.TestUtil.*;
|
||||
|
||||
/**
|
||||
* Tests {@link AuthorizationCodeRequestRedirectFilter}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class AuthorizationCodeRequestRedirectFilterTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||
new AuthorizationCodeRequestRedirectFilter(null, mock(AuthorizationRequestUriBuilder.class));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenAuthorizationRequestUriBuilderIsNullThenThrowIllegalArgumentException() {
|
||||
new AuthorizationCodeRequestRedirectFilter(mock(ClientRegistrationRepository.class), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenRequestDoesNotMatchClientThenContinueChain() throws Exception {
|
||||
ClientRegistration clientRegistration = googleClientRegistration();
|
||||
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString();
|
||||
AuthorizationCodeRequestRedirectFilter filter =
|
||||
setupFilter(authorizationUri, clientRegistration);
|
||||
|
||||
String requestURI = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI);
|
||||
request.setServletPath(requestURI);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenRequestMatchesClientThenRedirectForAuthorization() throws Exception {
|
||||
ClientRegistration clientRegistration = googleClientRegistration();
|
||||
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString();
|
||||
AuthorizationCodeRequestRedirectFilter filter =
|
||||
setupFilter(authorizationUri, clientRegistration);
|
||||
|
||||
String requestUri = AUTHORIZATION_BASE_URI + "/" + clientRegistration.getClientAlias();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyZeroInteractions(filterChain); // Request should not proceed up the chain
|
||||
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo(authorizationUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenRequestMatchesClientThenAuthorizationRequestSavedInSession() throws Exception {
|
||||
ClientRegistration clientRegistration = githubClientRegistration();
|
||||
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString();
|
||||
AuthorizationCodeRequestRedirectFilter filter =
|
||||
setupFilter(authorizationUri, clientRegistration);
|
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository();
|
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository);
|
||||
|
||||
String requestUri = AUTHORIZATION_BASE_URI + "/" + clientRegistration.getClientAlias();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyZeroInteractions(filterChain); // Request should not proceed up the chain
|
||||
|
||||
// The authorization request attributes are saved in the session before the redirect happens
|
||||
AuthorizationRequestAttributes authorizationRequestAttributes =
|
||||
authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||
assertThat(authorizationRequestAttributes).isNotNull();
|
||||
|
||||
assertThat(authorizationRequestAttributes.getAuthorizeUri()).isNotNull();
|
||||
assertThat(authorizationRequestAttributes.getGrantType()).isNotNull();
|
||||
assertThat(authorizationRequestAttributes.getResponseType()).isNotNull();
|
||||
assertThat(authorizationRequestAttributes.getClientId()).isNotNull();
|
||||
assertThat(authorizationRequestAttributes.getRedirectUri()).isNotNull();
|
||||
assertThat(authorizationRequestAttributes.getScopes()).isNotNull();
|
||||
assertThat(authorizationRequestAttributes.getState()).isNotNull();
|
||||
}
|
||||
|
||||
private AuthorizationCodeRequestRedirectFilter setupFilter(String authorizationUri,
|
||||
ClientRegistration... clientRegistrations) throws Exception {
|
||||
|
||||
AuthorizationRequestUriBuilder authorizationUriBuilder = mock(AuthorizationRequestUriBuilder.class);
|
||||
URI authorizationURI = new URI(authorizationUri);
|
||||
when(authorizationUriBuilder.build(any(AuthorizationRequestAttributes.class))).thenReturn(authorizationURI);
|
||||
|
||||
return setupFilter(authorizationUriBuilder, clientRegistrations);
|
||||
}
|
||||
|
||||
private AuthorizationCodeRequestRedirectFilter setupFilter(AuthorizationRequestUriBuilder authorizationUriBuilder,
|
||||
ClientRegistration... clientRegistrations) throws Exception {
|
||||
|
||||
ClientRegistrationRepository clientRegistrationRepository = clientRegistrationRepository(clientRegistrations);
|
||||
|
||||
AuthorizationCodeRequestRedirectFilter filter = new AuthorizationCodeRequestRedirectFilter(
|
||||
clientRegistrationRepository, authorizationUriBuilder);
|
||||
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationProperties;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
class TestUtil {
|
||||
static final String DEFAULT_SCHEME = "https";
|
||||
static final String DEFAULT_SERVER_NAME = "localhost";
|
||||
static final int DEFAULT_SERVER_PORT = 8080;
|
||||
static final String DEFAULT_SERVER_URL = DEFAULT_SCHEME + "://" + DEFAULT_SERVER_NAME + ":" + DEFAULT_SERVER_PORT;
|
||||
static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code";
|
||||
static final String AUTHORIZE_BASE_URI = "/oauth2/authorize/code";
|
||||
static final String GOOGLE_CLIENT_ALIAS = "google";
|
||||
static final String GITHUB_CLIENT_ALIAS = "github";
|
||||
|
||||
static ClientRegistrationRepository clientRegistrationRepository(ClientRegistration... clientRegistrations) {
|
||||
return new InMemoryClientRegistrationRepository(Arrays.asList(clientRegistrations));
|
||||
}
|
||||
|
||||
static ClientRegistration googleClientRegistration() {
|
||||
return googleClientRegistration(DEFAULT_SERVER_URL + AUTHORIZE_BASE_URI + "/" + GOOGLE_CLIENT_ALIAS);
|
||||
}
|
||||
|
||||
static ClientRegistration googleClientRegistration(String redirectUri) {
|
||||
ClientRegistrationProperties clientRegistrationProperties = new ClientRegistrationProperties();
|
||||
clientRegistrationProperties.setClientId("google-client-id");
|
||||
clientRegistrationProperties.setClientSecret("secret");
|
||||
clientRegistrationProperties.setAuthorizedGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
clientRegistrationProperties.setClientName("Google Client");
|
||||
clientRegistrationProperties.setClientAlias(GOOGLE_CLIENT_ALIAS);
|
||||
clientRegistrationProperties.setAuthorizationUri("https://accounts.google.com/o/oauth2/auth");
|
||||
clientRegistrationProperties.setTokenUri("https://accounts.google.com/o/oauth2/token");
|
||||
clientRegistrationProperties.setUserInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
|
||||
clientRegistrationProperties.setRedirectUri(redirectUri);
|
||||
clientRegistrationProperties.setScopes(Arrays.stream(new String[] {"openid", "email", "profile"}).collect(Collectors.toSet()));
|
||||
return new ClientRegistration.Builder(clientRegistrationProperties).build();
|
||||
}
|
||||
|
||||
static ClientRegistration githubClientRegistration() {
|
||||
return githubClientRegistration(DEFAULT_SERVER_URL + AUTHORIZE_BASE_URI + "/" + GITHUB_CLIENT_ALIAS);
|
||||
}
|
||||
|
||||
static ClientRegistration githubClientRegistration(String redirectUri) {
|
||||
ClientRegistrationProperties clientRegistrationProperties = new ClientRegistrationProperties();
|
||||
clientRegistrationProperties.setClientId("github-client-id");
|
||||
clientRegistrationProperties.setClientSecret("secret");
|
||||
clientRegistrationProperties.setAuthorizedGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
clientRegistrationProperties.setClientName("GitHub Client");
|
||||
clientRegistrationProperties.setClientAlias(GITHUB_CLIENT_ALIAS);
|
||||
clientRegistrationProperties.setAuthorizationUri("https://github.com/login/oauth/authorize");
|
||||
clientRegistrationProperties.setTokenUri("https://github.com/login/oauth/access_token");
|
||||
clientRegistrationProperties.setUserInfoUri("https://api.github.com/user");
|
||||
clientRegistrationProperties.setRedirectUri(redirectUri);
|
||||
clientRegistrationProperties.setScopes(Arrays.stream(new String[] {"user"}).collect(Collectors.toSet()));
|
||||
return new ClientRegistration.Builder(clientRegistrationProperties).build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user