Add support for OAuth 2.0 Login

Fixes gh-3907
This commit is contained in:
Joe Grandja
2017-03-20 16:18:08 -04:00
parent a38352c4cc
commit 829c386756
81 changed files with 6483 additions and 48 deletions

View File

@@ -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>&quot;authenticated&quot;</i>.
* </li>
* </ol>
*
* <p>
* <b>NOTE:</b> Steps 4-5 are <b>not</b> part of the authorization code grant flow and instead are
* <i>&quot;authentication flow&quot;</i> steps that are required in order to authenticate the end-user with the system.
*
* @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());
}
}
}

View File

@@ -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>&quot;authenticated&quot;</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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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>&quot;exchanging&quot;</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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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()));
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.client.authentication;
import org.springframework.security.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;
}
}

View File

@@ -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>&quot;authenticated&quot;</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;
}
}

View File

@@ -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>&quot;exchanges&quot;</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);
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.client.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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}