parent
8fbec3f0f1
commit
982fc360b2
|
@ -117,6 +117,10 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
|
||||||
order += STEP;
|
order += STEP;
|
||||||
put(AnonymousAuthenticationFilter.class, order);
|
put(AnonymousAuthenticationFilter.class, order);
|
||||||
order += STEP;
|
order += STEP;
|
||||||
|
filterToOrder.put(
|
||||||
|
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
|
||||||
|
order);
|
||||||
|
order += STEP;
|
||||||
put(SessionManagementFilter.class, order);
|
put(SessionManagementFilter.class, order);
|
||||||
order += STEP;
|
order += STEP;
|
||||||
put(ExceptionTranslationFilter.class, order);
|
put(ExceptionTranslationFilter.class, order);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,14 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web.builders;
|
package org.springframework.security.config.annotation.web.builders;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
@ -56,12 +48,13 @@ import org.springframework.security.config.annotation.web.configurers.SecurityCo
|
||||||
import org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.X509Configurer;
|
import org.springframework.security.config.annotation.web.configurers.X509Configurer;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.oauth2.OAuth2Configurer;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
|
|
||||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||||
import org.springframework.security.web.PortMapper;
|
import org.springframework.security.web.PortMapper;
|
||||||
import org.springframework.security.web.PortMapperImpl;
|
import org.springframework.security.web.PortMapperImpl;
|
||||||
|
@ -79,6 +72,13 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.filter.CorsFilter;
|
import org.springframework.web.filter.CorsFilter;
|
||||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link HttpSecurity} is similar to Spring Security's XML <http> element in the
|
* A {@link HttpSecurity} is similar to Spring Security's XML <http> element in the
|
||||||
* namespace configuration. It allows configuring web based security for specific http
|
* namespace configuration. It allows configuring web based security for specific http
|
||||||
|
@ -991,6 +991,18 @@ public final class HttpSecurity extends
|
||||||
return getOrApply(new OAuth2LoginConfigurer<>());
|
return getOrApply(new OAuth2LoginConfigurer<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures support for the <a target="_blank" href="https://tools.ietf.org/html/rfc6749">OAuth 2.0 Authorization Framework</a>.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @return the {@link OAuth2Configurer} for further customizations
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public OAuth2Configurer oauth2() throws Exception {
|
||||||
|
return getOrApply(new OAuth2Configurer());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures channel security. In order for this configuration to be useful at least
|
* Configures channel security. In order for this configuration to be useful at least
|
||||||
* one mapping to a required channel must be provided.
|
* one mapping to a required channel must be provided.
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.config.annotation.web.configurers.oauth2;
|
||||||
|
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AbstractHttpConfigurer} that provides support for the
|
||||||
|
* <a target="_blank" href="https://tools.ietf.org/html/rfc6749">OAuth 2.0 Authorization Framework</a>.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see HttpSecurity#oauth2()
|
||||||
|
* @see OAuth2ClientConfigurer
|
||||||
|
* @see AbstractHttpConfigurer
|
||||||
|
*/
|
||||||
|
public final class OAuth2Configurer extends AbstractHttpConfigurer<OAuth2Configurer, HttpSecurity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2ClientConfigurer} for configuring OAuth 2.0 Client support.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2ClientConfigurer}
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public OAuth2ClientConfigurer<HttpSecurity> client() throws Exception {
|
||||||
|
return this.getOrApply(new OAuth2ClientConfigurer<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <C extends AbstractHttpConfigurer<C, HttpSecurity>> C getOrApply(C configurer) throws Exception {
|
||||||
|
C existingConfigurer = (C) this.getBuilder().getConfigurer(configurer.getClass());
|
||||||
|
if (existingConfigurer != null) {
|
||||||
|
return existingConfigurer;
|
||||||
|
}
|
||||||
|
return this.getBuilder().apply(configurer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
@ -86,7 +85,7 @@ public final class ImplicitGrantConfigurer<B extends HttpSecurityBuilder<B>> ext
|
||||||
@Override
|
@Override
|
||||||
public void configure(B http) throws Exception {
|
public void configure(B http) throws Exception {
|
||||||
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
|
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
|
||||||
this.getClientRegistrationRepository(), this.getAuthorizationRequestBaseUri());
|
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()), this.getAuthorizationRequestBaseUri());
|
||||||
http.addFilter(this.postProcess(authorizationRequestFilter));
|
http.addFilter(this.postProcess(authorizationRequestFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,17 +94,4 @@ public final class ImplicitGrantConfigurer<B extends HttpSecurityBuilder<B>> ext
|
||||||
this.authorizationRequestBaseUri :
|
this.authorizationRequestBaseUri :
|
||||||
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
|
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientRegistrationRepository getClientRegistrationRepository() {
|
|
||||||
ClientRegistrationRepository clientRegistrationRepository = this.getBuilder().getSharedObject(ClientRegistrationRepository.class);
|
|
||||||
if (clientRegistrationRepository == null) {
|
|
||||||
clientRegistrationRepository = this.getClientRegistrationRepositoryBean();
|
|
||||||
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
|
|
||||||
}
|
|
||||||
return clientRegistrationRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClientRegistrationRepository getClientRegistrationRepositoryBean() {
|
|
||||||
return this.getBuilder().getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.config.annotation.web.configurers.oauth2.client;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
|
||||||
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Client support.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The following configuration options are available:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #authorizationCodeGrant()} - enables the OAuth 2.0 Authorization Code Grant</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Defaults are provided for all configuration options with the only required configuration
|
||||||
|
* being {@link #clientRegistrationRepository(ClientRegistrationRepository)}.
|
||||||
|
* Alternatively, a {@link ClientRegistrationRepository} {@code @Bean} may be registered instead.
|
||||||
|
*
|
||||||
|
* <h2>Security Filters</h2>
|
||||||
|
*
|
||||||
|
* The following {@code Filter}'s are populated when {@link #authorizationCodeGrant()} is configured:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link OAuth2AuthorizationRequestRedirectFilter}</li>
|
||||||
|
* <li>{@link OAuth2AuthorizationCodeGrantFilter}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>Shared Objects Created</h2>
|
||||||
|
*
|
||||||
|
* The following shared objects are populated:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link ClientRegistrationRepository} (required)</li>
|
||||||
|
* <li>{@link OAuth2AuthorizedClientService} (optional)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>Shared Objects Used</h2>
|
||||||
|
*
|
||||||
|
* The following shared objects are used:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link ClientRegistrationRepository}</li>
|
||||||
|
* <li>{@link OAuth2AuthorizedClientService}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see OAuth2AuthorizationRequestRedirectFilter
|
||||||
|
* @see OAuth2AuthorizationCodeGrantFilter
|
||||||
|
* @see ClientRegistrationRepository
|
||||||
|
* @see OAuth2AuthorizedClientService
|
||||||
|
* @see AbstractHttpConfigurer
|
||||||
|
*/
|
||||||
|
public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>> extends
|
||||||
|
AbstractHttpConfigurer<OAuth2ClientConfigurer<B>, B> {
|
||||||
|
|
||||||
|
private AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the repository of client registrations.
|
||||||
|
*
|
||||||
|
* @param clientRegistrationRepository the repository of client registrations
|
||||||
|
* @return the {@link OAuth2ClientConfigurer} for further configuration
|
||||||
|
*/
|
||||||
|
public OAuth2ClientConfigurer<B> clientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) {
|
||||||
|
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||||
|
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the service for authorized client(s).
|
||||||
|
*
|
||||||
|
* @param authorizedClientService the authorized client service
|
||||||
|
* @return the {@link OAuth2ClientConfigurer} for further configuration
|
||||||
|
*/
|
||||||
|
public OAuth2ClientConfigurer<B> authorizedClientService(OAuth2AuthorizedClientService authorizedClientService) {
|
||||||
|
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||||
|
this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AuthorizationCodeGrantConfigurer} for configuring the OAuth 2.0 Authorization Code Grant.
|
||||||
|
*
|
||||||
|
* @return the {@link AuthorizationCodeGrantConfigurer}
|
||||||
|
*/
|
||||||
|
public AuthorizationCodeGrantConfigurer authorizationCodeGrant() {
|
||||||
|
if (this.authorizationCodeGrantConfigurer == null) {
|
||||||
|
this.authorizationCodeGrantConfigurer = new AuthorizationCodeGrantConfigurer();
|
||||||
|
}
|
||||||
|
return this.authorizationCodeGrantConfigurer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for the OAuth 2.0 Authorization Code Grant.
|
||||||
|
*/
|
||||||
|
public class AuthorizationCodeGrantConfigurer {
|
||||||
|
private final AuthorizationEndpointConfig authorizationEndpointConfig = new AuthorizationEndpointConfig();
|
||||||
|
private final TokenEndpointConfig tokenEndpointConfig = new TokenEndpointConfig();
|
||||||
|
|
||||||
|
private AuthorizationCodeGrantConfigurer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AuthorizationEndpointConfig} for configuring the Authorization Server's Authorization Endpoint.
|
||||||
|
*
|
||||||
|
* @return the {@link AuthorizationEndpointConfig}
|
||||||
|
*/
|
||||||
|
public AuthorizationEndpointConfig authorizationEndpoint() {
|
||||||
|
return this.authorizationEndpointConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for the Authorization Server's Authorization Endpoint.
|
||||||
|
*/
|
||||||
|
public class AuthorizationEndpointConfig {
|
||||||
|
private String authorizationRequestBaseUri;
|
||||||
|
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
|
||||||
|
|
||||||
|
private AuthorizationEndpointConfig() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base {@code URI} used for authorization requests.
|
||||||
|
*
|
||||||
|
* @param authorizationRequestBaseUri the base {@code URI} used for authorization requests
|
||||||
|
* @return the {@link AuthorizationEndpointConfig} for further configuration
|
||||||
|
*/
|
||||||
|
public AuthorizationEndpointConfig baseUri(String authorizationRequestBaseUri) {
|
||||||
|
Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
|
||||||
|
this.authorizationRequestBaseUri = authorizationRequestBaseUri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the repository used for storing {@link OAuth2AuthorizationRequest}'s.
|
||||||
|
*
|
||||||
|
* @param authorizationRequestRepository the repository used for storing {@link OAuth2AuthorizationRequest}'s
|
||||||
|
* @return the {@link AuthorizationEndpointConfig} for further configuration
|
||||||
|
*/
|
||||||
|
public AuthorizationEndpointConfig authorizationRequestRepository(
|
||||||
|
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository) {
|
||||||
|
|
||||||
|
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
|
||||||
|
this.authorizationRequestRepository = authorizationRequestRepository;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AuthorizationCodeGrantConfigurer} for further configuration.
|
||||||
|
*
|
||||||
|
* @return the {@link AuthorizationCodeGrantConfigurer}
|
||||||
|
*/
|
||||||
|
public AuthorizationCodeGrantConfigurer and() {
|
||||||
|
return AuthorizationCodeGrantConfigurer.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link TokenEndpointConfig} for configuring the Authorization Server's Token Endpoint.
|
||||||
|
*
|
||||||
|
* @return the {@link TokenEndpointConfig}
|
||||||
|
*/
|
||||||
|
public TokenEndpointConfig tokenEndpoint() {
|
||||||
|
return this.tokenEndpointConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for the Authorization Server's Token Endpoint.
|
||||||
|
*/
|
||||||
|
public class TokenEndpointConfig {
|
||||||
|
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
|
||||||
|
private TokenEndpointConfig() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the client used for requesting the access token credential from the Token Endpoint.
|
||||||
|
*
|
||||||
|
* @param accessTokenResponseClient the client used for requesting the access token credential from the Token Endpoint
|
||||||
|
* @return the {@link TokenEndpointConfig} for further configuration
|
||||||
|
*/
|
||||||
|
public TokenEndpointConfig accessTokenResponseClient(
|
||||||
|
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
|
||||||
|
|
||||||
|
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||||
|
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AuthorizationCodeGrantConfigurer} for further configuration.
|
||||||
|
*
|
||||||
|
* @return the {@link AuthorizationCodeGrantConfigurer}
|
||||||
|
*/
|
||||||
|
public AuthorizationCodeGrantConfigurer and() {
|
||||||
|
return AuthorizationCodeGrantConfigurer.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2ClientConfigurer} for further configuration.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2ClientConfigurer}
|
||||||
|
*/
|
||||||
|
public OAuth2ClientConfigurer<B> and() {
|
||||||
|
return OAuth2ClientConfigurer.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(B builder) throws Exception {
|
||||||
|
if (this.authorizationCodeGrantConfigurer != null) {
|
||||||
|
this.init(builder, this.authorizationCodeGrantConfigurer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(B builder) throws Exception {
|
||||||
|
if (this.authorizationCodeGrantConfigurer != null) {
|
||||||
|
this.configure(builder, this.authorizationCodeGrantConfigurer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(B builder, AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer) throws Exception {
|
||||||
|
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient =
|
||||||
|
authorizationCodeGrantConfigurer.tokenEndpointConfig.accessTokenResponseClient;
|
||||||
|
if (accessTokenResponseClient == null) {
|
||||||
|
accessTokenResponseClient = new NimbusAuthorizationCodeTokenResponseClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationProvider(accessTokenResponseClient);
|
||||||
|
builder.authenticationProvider(this.postProcess(authorizationCodeAuthenticationProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configure(B builder, AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer) throws Exception {
|
||||||
|
String authorizationRequestBaseUri = authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestBaseUri;
|
||||||
|
if (authorizationRequestBaseUri == null) {
|
||||||
|
authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
|
||||||
|
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder), authorizationRequestBaseUri);
|
||||||
|
|
||||||
|
if (authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository != null) {
|
||||||
|
authorizationRequestFilter.setAuthorizationRequestRepository(
|
||||||
|
authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository);
|
||||||
|
}
|
||||||
|
builder.addFilter(this.postProcess(authorizationRequestFilter));
|
||||||
|
|
||||||
|
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeGrantFilter authorizationCodeGrantFilter = new OAuth2AuthorizationCodeGrantFilter(
|
||||||
|
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder),
|
||||||
|
OAuth2ClientConfigurerUtils.getAuthorizedClientService(builder),
|
||||||
|
authenticationManager);
|
||||||
|
|
||||||
|
if (authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository != null) {
|
||||||
|
authorizationCodeGrantFilter.setAuthorizationRequestRepository(
|
||||||
|
authorizationCodeGrantConfigurer.authorizationEndpointConfig.authorizationRequestRepository);
|
||||||
|
}
|
||||||
|
builder.addFilter(this.postProcess(authorizationCodeGrantFilter));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.config.annotation.web.configurers.oauth2.client;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||||
|
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for the OAuth 2.0 Client {@link AbstractHttpConfigurer}'s.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
final class OAuth2ClientConfigurerUtils {
|
||||||
|
|
||||||
|
private OAuth2ClientConfigurerUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static <B extends HttpSecurityBuilder<B>> ClientRegistrationRepository getClientRegistrationRepository(B builder) {
|
||||||
|
ClientRegistrationRepository clientRegistrationRepository = builder.getSharedObject(ClientRegistrationRepository.class);
|
||||||
|
if (clientRegistrationRepository == null) {
|
||||||
|
clientRegistrationRepository = getClientRegistrationRepositoryBean(builder);
|
||||||
|
builder.setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
|
||||||
|
}
|
||||||
|
return clientRegistrationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <B extends HttpSecurityBuilder<B>> ClientRegistrationRepository getClientRegistrationRepositoryBean(B builder) {
|
||||||
|
return builder.getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizedClientService getAuthorizedClientService(B builder) {
|
||||||
|
OAuth2AuthorizedClientService authorizedClientService = builder.getSharedObject(OAuth2AuthorizedClientService.class);
|
||||||
|
if (authorizedClientService == null) {
|
||||||
|
authorizedClientService = getAuthorizedClientServiceBean(builder);
|
||||||
|
if (authorizedClientService == null) {
|
||||||
|
authorizedClientService = new InMemoryOAuth2AuthorizedClientService(getClientRegistrationRepository(builder));
|
||||||
|
}
|
||||||
|
builder.setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
|
||||||
|
}
|
||||||
|
return authorizedClientService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizedClientService getAuthorizedClientServiceBean(B builder) {
|
||||||
|
Map<String, OAuth2AuthorizedClientService> authorizedClientServiceMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||||
|
builder.getSharedObject(ApplicationContext.class), OAuth2AuthorizedClientService.class);
|
||||||
|
if (authorizedClientServiceMap.size() > 1) {
|
||||||
|
throw new NoUniqueBeanDefinitionException(OAuth2AuthorizedClientService.class, authorizedClientServiceMap.size(),
|
||||||
|
"Only one matching @Bean of type " + OAuth2AuthorizedClientService.class.getName() + " should be registered.");
|
||||||
|
}
|
||||||
|
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,6 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
|
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
||||||
|
@ -376,8 +375,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
public void init(B http) throws Exception {
|
public void init(B http) throws Exception {
|
||||||
OAuth2LoginAuthenticationFilter authenticationFilter =
|
OAuth2LoginAuthenticationFilter authenticationFilter =
|
||||||
new OAuth2LoginAuthenticationFilter(
|
new OAuth2LoginAuthenticationFilter(
|
||||||
this.getClientRegistrationRepository(),
|
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
|
||||||
this.getAuthorizedClientService(),
|
OAuth2ClientConfigurerUtils.getAuthorizedClientService(this.getBuilder()),
|
||||||
OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
||||||
this.setAuthenticationFilter(authenticationFilter);
|
this.setAuthenticationFilter(authenticationFilter);
|
||||||
this.loginProcessingUrl(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
this.loginProcessingUrl(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
||||||
|
@ -442,7 +441,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
|
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
|
||||||
this.getClientRegistrationRepository(), authorizationRequestBaseUri);
|
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()), authorizationRequestBaseUri);
|
||||||
|
|
||||||
if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
|
if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
|
||||||
authorizationRequestFilter.setAuthorizationRequestRepository(
|
authorizationRequestFilter.setAuthorizationRequestRepository(
|
||||||
|
@ -466,41 +465,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
return new AntPathRequestMatcher(loginProcessingUrl);
|
return new AntPathRequestMatcher(loginProcessingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientRegistrationRepository getClientRegistrationRepository() {
|
|
||||||
ClientRegistrationRepository clientRegistrationRepository =
|
|
||||||
this.getBuilder().getSharedObject(ClientRegistrationRepository.class);
|
|
||||||
if (clientRegistrationRepository == null) {
|
|
||||||
clientRegistrationRepository = this.getClientRegistrationRepositoryBean();
|
|
||||||
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
|
|
||||||
}
|
|
||||||
return clientRegistrationRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClientRegistrationRepository getClientRegistrationRepositoryBean() {
|
|
||||||
return this.getBuilder().getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2AuthorizedClientService getAuthorizedClientService() {
|
|
||||||
OAuth2AuthorizedClientService authorizedClientService =
|
|
||||||
this.getBuilder().getSharedObject(OAuth2AuthorizedClientService.class);
|
|
||||||
if (authorizedClientService == null) {
|
|
||||||
authorizedClientService = this.getAuthorizedClientServiceBean();
|
|
||||||
if (authorizedClientService == null) {
|
|
||||||
authorizedClientService = new InMemoryOAuth2AuthorizedClientService(this.getClientRegistrationRepository());
|
|
||||||
}
|
|
||||||
this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
|
|
||||||
}
|
|
||||||
return authorizedClientService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2AuthorizedClientService getAuthorizedClientServiceBean() {
|
|
||||||
Map<String, OAuth2AuthorizedClientService> authorizedClientServiceMap =
|
|
||||||
BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
|
||||||
this.getBuilder().getSharedObject(ApplicationContext.class),
|
|
||||||
OAuth2AuthorizedClientService.class);
|
|
||||||
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GrantedAuthoritiesMapper getGrantedAuthoritiesMapper() {
|
private GrantedAuthoritiesMapper getGrantedAuthoritiesMapper() {
|
||||||
GrantedAuthoritiesMapper grantedAuthoritiesMapper =
|
GrantedAuthoritiesMapper grantedAuthoritiesMapper =
|
||||||
this.getBuilder().getSharedObject(GrantedAuthoritiesMapper.class);
|
this.getBuilder().getSharedObject(GrantedAuthoritiesMapper.class);
|
||||||
|
@ -528,7 +492,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<ClientRegistration> clientRegistrations = null;
|
Iterable<ClientRegistration> clientRegistrations = null;
|
||||||
ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
|
ClientRegistrationRepository clientRegistrationRepository =
|
||||||
|
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder());
|
||||||
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
|
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
|
||||||
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
|
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
|
||||||
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
|
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
|
||||||
|
@ -580,5 +545,4 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
|
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public enum CommonOAuth2Provider {
|
||||||
@Override
|
@Override
|
||||||
public Builder getBuilder(String registrationId) {
|
public Builder getBuilder(String registrationId) {
|
||||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||||
ClientAuthenticationMethod.BASIC, DEFAULT_LOGIN_REDIRECT_URL);
|
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||||
builder.scope("openid", "profile", "email");
|
builder.scope("openid", "profile", "email");
|
||||||
builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
|
builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
|
||||||
builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
|
builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
|
||||||
|
@ -53,7 +53,7 @@ public enum CommonOAuth2Provider {
|
||||||
@Override
|
@Override
|
||||||
public Builder getBuilder(String registrationId) {
|
public Builder getBuilder(String registrationId) {
|
||||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||||
ClientAuthenticationMethod.BASIC, DEFAULT_LOGIN_REDIRECT_URL);
|
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||||
builder.scope("read:user");
|
builder.scope("read:user");
|
||||||
builder.authorizationUri("https://github.com/login/oauth/authorize");
|
builder.authorizationUri("https://github.com/login/oauth/authorize");
|
||||||
builder.tokenUri("https://github.com/login/oauth/access_token");
|
builder.tokenUri("https://github.com/login/oauth/access_token");
|
||||||
|
@ -69,7 +69,7 @@ public enum CommonOAuth2Provider {
|
||||||
@Override
|
@Override
|
||||||
public Builder getBuilder(String registrationId) {
|
public Builder getBuilder(String registrationId) {
|
||||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||||
ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
|
ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
|
||||||
builder.scope("public_profile", "email");
|
builder.scope("public_profile", "email");
|
||||||
builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");
|
builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");
|
||||||
builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");
|
builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");
|
||||||
|
@ -85,7 +85,7 @@ public enum CommonOAuth2Provider {
|
||||||
@Override
|
@Override
|
||||||
public Builder getBuilder(String registrationId) {
|
public Builder getBuilder(String registrationId) {
|
||||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||||
ClientAuthenticationMethod.BASIC, DEFAULT_LOGIN_REDIRECT_URL);
|
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||||
builder.scope("openid", "profile", "email", "address", "phone");
|
builder.scope("openid", "profile", "email", "address", "phone");
|
||||||
builder.userNameAttributeName(IdTokenClaimNames.SUB);
|
builder.userNameAttributeName(IdTokenClaimNames.SUB);
|
||||||
builder.clientName("Okta");
|
builder.clientName("Okta");
|
||||||
|
@ -93,7 +93,7 @@ public enum CommonOAuth2Provider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login/oauth2/code/{registrationId}";
|
private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";
|
||||||
|
|
||||||
protected final ClientRegistration.Builder getBuilder(String registrationId,
|
protected final ClientRegistration.Builder getBuilder(String registrationId,
|
||||||
ClientAuthenticationMethod method, String redirectUri) {
|
ClientAuthenticationMethod method, String redirectUri) {
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.config.annotation.web.configurers.oauth2.client;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
|
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2ClientConfigurer}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2ClientConfigurerTests {
|
||||||
|
private static ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
|
private static OAuth2AuthorizedClientService authorizedClientService;
|
||||||
|
|
||||||
|
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
private ClientRegistration registration1;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
this.registration1 = ClientRegistration.withRegistrationId("registration-1")
|
||||||
|
.clientId("client-1")
|
||||||
|
.clientSecret("secret")
|
||||||
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.redirectUriTemplate("{baseUrl}/client-1")
|
||||||
|
.scope("user")
|
||||||
|
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||||
|
.tokenUri("https://provider.com/oauth2/token")
|
||||||
|
.userInfoUri("https://provider.com/oauth2/user")
|
||||||
|
.userNameAttributeName("id")
|
||||||
|
.clientName("client-1")
|
||||||
|
.build();
|
||||||
|
clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1);
|
||||||
|
authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
|
||||||
|
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
|
||||||
|
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||||
|
.expiresIn(300)
|
||||||
|
.build();
|
||||||
|
accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
|
||||||
|
when(accessTokenResponseClient.getTokenResponse(any(OAuth2AuthorizationCodeGrantRequest.class))).thenReturn(accessTokenResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void configureWhenAuthorizationCodeRequestThenRedirectForAuthorization() throws Exception {
|
||||||
|
this.spring.register(OAuth2ClientConfig.class).autowire();
|
||||||
|
|
||||||
|
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andReturn();
|
||||||
|
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http://localhost/client-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void configureWhenAuthorizationCodeResponseSuccessThenAuthorizedClientSaved() throws Exception {
|
||||||
|
this.spring.register(OAuth2ClientConfig.class).autowire();
|
||||||
|
|
||||||
|
// Setup the Authorization Request in the session
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId());
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||||
|
.authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri())
|
||||||
|
.clientId(this.registration1.getClientId())
|
||||||
|
.redirectUri("http://localhost/client-1")
|
||||||
|
.state("state")
|
||||||
|
.additionalParameters(additionalParameters)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
||||||
|
new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
||||||
|
|
||||||
|
MockHttpSession session = (MockHttpSession) request.getSession();
|
||||||
|
|
||||||
|
String principalName = "user1";
|
||||||
|
|
||||||
|
this.mockMvc.perform(get("/client-1")
|
||||||
|
.param(OAuth2ParameterNames.CODE, "code")
|
||||||
|
.param(OAuth2ParameterNames.STATE, "state")
|
||||||
|
.with(user(principalName))
|
||||||
|
.session(session))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("http://localhost/client-1"));
|
||||||
|
|
||||||
|
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
|
||||||
|
this.registration1.getRegistrationId(), principalName);
|
||||||
|
assertThat(authorizedClient).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.oauth2()
|
||||||
|
.client()
|
||||||
|
.clientRegistrationRepository(clientRegistrationRepository)
|
||||||
|
.authorizedClientService(authorizedClientService)
|
||||||
|
.authorizationCodeGrant()
|
||||||
|
.tokenEndpoint()
|
||||||
|
.accessTokenResponseClient(accessTokenResponseClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
*/
|
*/
|
||||||
public class CommonOAuth2ProviderTests {
|
public class CommonOAuth2ProviderTests {
|
||||||
|
|
||||||
private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login/oauth2/code/{registrationId}";
|
private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBuilderWhenGoogleShouldHaveGoogleSettings() throws Exception {
|
public void getBuilderWhenGoogleShouldHaveGoogleSettings() throws Exception {
|
||||||
|
@ -51,7 +51,7 @@ public class CommonOAuth2ProviderTests {
|
||||||
.isEqualTo(ClientAuthenticationMethod.BASIC);
|
.isEqualTo(ClientAuthenticationMethod.BASIC);
|
||||||
assertThat(registration.getAuthorizationGrantType())
|
assertThat(registration.getAuthorizationGrantType())
|
||||||
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||||
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
|
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
|
||||||
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email");
|
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email");
|
||||||
assertThat(registration.getClientName()).isEqualTo("Google");
|
assertThat(registration.getClientName()).isEqualTo("Google");
|
||||||
assertThat(registration.getRegistrationId()).isEqualTo("123");
|
assertThat(registration.getRegistrationId()).isEqualTo("123");
|
||||||
|
@ -74,7 +74,7 @@ public class CommonOAuth2ProviderTests {
|
||||||
.isEqualTo(ClientAuthenticationMethod.BASIC);
|
.isEqualTo(ClientAuthenticationMethod.BASIC);
|
||||||
assertThat(registration.getAuthorizationGrantType())
|
assertThat(registration.getAuthorizationGrantType())
|
||||||
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||||
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
|
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
|
||||||
assertThat(registration.getScopes()).containsOnly("read:user");
|
assertThat(registration.getScopes()).containsOnly("read:user");
|
||||||
assertThat(registration.getClientName()).isEqualTo("GitHub");
|
assertThat(registration.getClientName()).isEqualTo("GitHub");
|
||||||
assertThat(registration.getRegistrationId()).isEqualTo("123");
|
assertThat(registration.getRegistrationId()).isEqualTo("123");
|
||||||
|
@ -97,7 +97,7 @@ public class CommonOAuth2ProviderTests {
|
||||||
.isEqualTo(ClientAuthenticationMethod.POST);
|
.isEqualTo(ClientAuthenticationMethod.POST);
|
||||||
assertThat(registration.getAuthorizationGrantType())
|
assertThat(registration.getAuthorizationGrantType())
|
||||||
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||||
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
|
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
|
||||||
assertThat(registration.getScopes()).containsOnly("public_profile", "email");
|
assertThat(registration.getScopes()).containsOnly("public_profile", "email");
|
||||||
assertThat(registration.getClientName()).isEqualTo("Facebook");
|
assertThat(registration.getClientName()).isEqualTo("Facebook");
|
||||||
assertThat(registration.getRegistrationId()).isEqualTo("123");
|
assertThat(registration.getRegistrationId()).isEqualTo("123");
|
||||||
|
@ -122,7 +122,7 @@ public class CommonOAuth2ProviderTests {
|
||||||
.isEqualTo(ClientAuthenticationMethod.BASIC);
|
.isEqualTo(ClientAuthenticationMethod.BASIC);
|
||||||
assertThat(registration.getAuthorizationGrantType())
|
assertThat(registration.getAuthorizationGrantType())
|
||||||
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||||
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_LOGIN_REDIRECT_URL);
|
assertThat(registration.getRedirectUriTemplate()).isEqualTo(DEFAULT_REDIRECT_URL);
|
||||||
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email",
|
assertThat(registration.getScopes()).containsOnly("openid", "profile", "email",
|
||||||
"address", "phone");
|
"address", "phone");
|
||||||
assertThat(registration.getClientName()).isEqualTo("Okta");
|
assertThat(registration.getClientName()).isEqualTo("Okta");
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown when an OAuth 2.0 Client is required
|
||||||
|
* to obtain authorization from the Resource Owner.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see OAuth2AuthorizedClient
|
||||||
|
*/
|
||||||
|
public class ClientAuthorizationRequiredException extends OAuth2ClientException {
|
||||||
|
private final String clientRegistrationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code ClientAuthorizationRequiredException} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param clientRegistrationId the identifier for the client's registration
|
||||||
|
*/
|
||||||
|
public ClientAuthorizationRequiredException(String clientRegistrationId) {
|
||||||
|
this(clientRegistrationId, "Authorization required for Client Registration Id: " + clientRegistrationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code ClientAuthorizationRequiredException} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param clientRegistrationId the identifier for the client's registration
|
||||||
|
* @param message the detail message
|
||||||
|
*/
|
||||||
|
public ClientAuthorizationRequiredException(String clientRegistrationId, String message) {
|
||||||
|
super(message);
|
||||||
|
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||||
|
this.clientRegistrationId = clientRegistrationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identifier for the client's registration.
|
||||||
|
*
|
||||||
|
* @return the identifier for the client's registration
|
||||||
|
*/
|
||||||
|
public String getClientRegistrationId() {
|
||||||
|
return this.clientRegistrationId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base exception for OAuth 2.0 Client related errors.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
public class OAuth2ClientException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2ClientException} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param message the detail message
|
||||||
|
*/
|
||||||
|
public OAuth2ClientException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2ClientException} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param message the detail message
|
||||||
|
* @param cause the root cause
|
||||||
|
*/
|
||||||
|
public OAuth2ClientException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.AuthenticationProvider;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of an {@link AuthenticationProvider} for the OAuth 2.0 Authorization Code Grant.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This {@link AuthenticationProvider} is responsible for authenticating
|
||||||
|
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||||
|
* and if valid, exchanging it for an Access Token credential.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see OAuth2AuthorizationCodeAuthenticationToken
|
||||||
|
* @see OAuth2AccessTokenResponseClient
|
||||||
|
* @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 OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param accessTokenResponseClient the client used for requesting the access token credential from the Token Endpoint
|
||||||
|
*/
|
||||||
|
public OAuth2AuthorizationCodeAuthenticationProvider(
|
||||||
|
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
|
||||||
|
|
||||||
|
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||||
|
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
||||||
|
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||||
|
|
||||||
|
OAuth2AuthorizationExchangeValidator.validate(
|
||||||
|
authorizationCodeAuthentication.getAuthorizationExchange());
|
||||||
|
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse =
|
||||||
|
this.accessTokenResponseClient.getTokenResponse(
|
||||||
|
new OAuth2AuthorizationCodeGrantRequest(
|
||||||
|
authorizationCodeAuthentication.getClientRegistration(),
|
||||||
|
authorizationCodeAuthentication.getAuthorizationExchange()));
|
||||||
|
|
||||||
|
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authenticationResult =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
authorizationCodeAuthentication.getClientRegistration(),
|
||||||
|
authorizationCodeAuthentication.getAuthorizationExchange(),
|
||||||
|
accessToken);
|
||||||
|
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
|
||||||
|
|
||||||
|
return authenticationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.SpringSecurityCoreVersion;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AbstractAuthenticationToken} for the OAuth 2.0 Authorization Code Grant.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see AbstractAuthenticationToken
|
||||||
|
* @see ClientRegistration
|
||||||
|
* @see OAuth2AuthorizationExchange
|
||||||
|
* @see OAuth2AccessToken
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||||
|
*/
|
||||||
|
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
|
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||||
|
private ClientRegistration clientRegistration;
|
||||||
|
private OAuth2AuthorizationExchange authorizationExchange;
|
||||||
|
private OAuth2AccessToken accessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor should be used when the Authorization Request/Response is complete.
|
||||||
|
*
|
||||||
|
* @param clientRegistration the client registration
|
||||||
|
* @param authorizationExchange the authorization exchange
|
||||||
|
*/
|
||||||
|
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
|
||||||
|
OAuth2AuthorizationExchange authorizationExchange) {
|
||||||
|
super(Collections.emptyList());
|
||||||
|
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||||
|
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
|
||||||
|
this.clientRegistration = clientRegistration;
|
||||||
|
this.authorizationExchange = authorizationExchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor should be used when the Access Token Request/Response is complete,
|
||||||
|
* which indicates that the Authorization Code Grant flow has fully completed.
|
||||||
|
*
|
||||||
|
* @param clientRegistration the client registration
|
||||||
|
* @param authorizationExchange the authorization exchange
|
||||||
|
* @param accessToken the access token credential
|
||||||
|
*/
|
||||||
|
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
|
||||||
|
OAuth2AuthorizationExchange authorizationExchange,
|
||||||
|
OAuth2AccessToken accessToken) {
|
||||||
|
this(clientRegistration, authorizationExchange);
|
||||||
|
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.setAuthenticated(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.clientRegistration.getClientId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return this.accessToken != null ?
|
||||||
|
this.accessToken.getTokenValue() :
|
||||||
|
this.authorizationExchange.getAuthorizationResponse().getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link ClientRegistration client registration}.
|
||||||
|
*
|
||||||
|
* @return the {@link ClientRegistration}
|
||||||
|
*/
|
||||||
|
public ClientRegistration getClientRegistration() {
|
||||||
|
return this.clientRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2AuthorizationExchange authorization exchange}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2AuthorizationExchange}
|
||||||
|
*/
|
||||||
|
public OAuth2AuthorizationExchange getAuthorizationExchange() {
|
||||||
|
return this.authorizationExchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2AccessToken access token}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2AccessToken}
|
||||||
|
*/
|
||||||
|
public OAuth2AccessToken getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A validator for an "exchange" of an OAuth 2.0 Authorization Request and Response.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see OAuth2AuthorizationExchange
|
||||||
|
*/
|
||||||
|
final class OAuth2AuthorizationExchangeValidator {
|
||||||
|
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";
|
||||||
|
|
||||||
|
static void validate(OAuth2AuthorizationExchange authorizationExchange) {
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorizationExchange.getAuthorizationRequest();
|
||||||
|
OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
|
||||||
|
|
||||||
|
if (authorizationResponse.statusError()) {
|
||||||
|
throw new OAuth2AuthenticationException(
|
||||||
|
authorizationResponse.getError(), authorizationResponse.getError().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||||
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
|
||||||
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,11 +25,7 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCo
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@ -60,8 +56,6 @@ import java.util.Collection;
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</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 OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
|
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
|
||||||
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 OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
|
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
|
||||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||||
|
@ -97,25 +91,8 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
|
OAuth2AuthorizationExchangeValidator.validate(
|
||||||
.getAuthorizationExchange().getAuthorizationRequest();
|
authorizationCodeAuthentication.getAuthorizationExchange());
|
||||||
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
|
|
||||||
.getAuthorizationExchange().getAuthorizationResponse();
|
|
||||||
|
|
||||||
if (authorizationResponse.statusError()) {
|
|
||||||
throw new OAuth2AuthenticationException(
|
|
||||||
authorizationResponse.getError(), authorizationResponse.getError().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
|
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
|
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2AccessTokenResponse accessTokenResponse =
|
OAuth2AccessTokenResponse accessTokenResponse =
|
||||||
this.accessTokenResponseClient.getTokenResponse(
|
this.accessTokenResponseClient.getTokenResponse(
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||||
|
import org.springframework.security.web.RedirectStrategy;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
|
import org.springframework.security.web.savedrequest.RequestCache;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
||||||
|
* which handles the processing of the OAuth 2.0 Authorization Response.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The OAuth 2.0 Authorization Response is processed as follows:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* Assuming the End-User (Resource Owner) has granted access to the Client, the Authorization Server will append the
|
||||||
|
* {@link OAuth2ParameterNames#CODE code} and {@link OAuth2ParameterNames#STATE state} parameters
|
||||||
|
* to the {@link OAuth2ParameterNames#REDIRECT_URI redirect_uri} (provided in the Authorization Request)
|
||||||
|
* and redirect the End-User's user-agent back to this {@code Filter} (the Client).
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* This {@code Filter} will then create an {@link OAuth2AuthorizationCodeAuthenticationToken} with
|
||||||
|
* the {@link OAuth2ParameterNames#CODE code} received and
|
||||||
|
* delegate it to the {@link AuthenticationManager} to authenticate.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Upon a successful authentication, an {@link OAuth2AuthorizedClient Authorized Client} is created by associating the
|
||||||
|
* {@link OAuth2AuthorizationCodeAuthenticationToken#getClientRegistration() client} to the
|
||||||
|
* {@link OAuth2AuthorizationCodeAuthenticationToken#getAccessToken() access token} and current {@code Principal}
|
||||||
|
* and saving it via the {@link OAuth2AuthorizedClientService}.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see OAuth2AuthorizationCodeAuthenticationToken
|
||||||
|
* @see OAuth2AuthorizationCodeAuthenticationProvider
|
||||||
|
* @see OAuth2AuthorizationRequest
|
||||||
|
* @see OAuth2AuthorizationResponse
|
||||||
|
* @see AuthorizationRequestRepository
|
||||||
|
* @see OAuth2AuthorizationRequestRedirectFilter
|
||||||
|
* @see ClientRegistrationRepository
|
||||||
|
* @see OAuth2AuthorizedClient
|
||||||
|
* @see OAuth2AuthorizedClientService
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</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 OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
|
||||||
|
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
private final OAuth2AuthorizedClientService authorizedClientService;
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
||||||
|
new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||||
|
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
|
||||||
|
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||||
|
private final RequestCache requestCache = new HttpSessionRequestCache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2AuthorizationCodeGrantFilter} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param clientRegistrationRepository the repository of client registrations
|
||||||
|
* @param authorizedClientService the authorized client service
|
||||||
|
* @param authenticationManager the authentication manager
|
||||||
|
*/
|
||||||
|
public OAuth2AuthorizationCodeGrantFilter(ClientRegistrationRepository clientRegistrationRepository,
|
||||||
|
OAuth2AuthorizedClientService authorizedClientService,
|
||||||
|
AuthenticationManager authenticationManager) {
|
||||||
|
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||||
|
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||||
|
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||||
|
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||||
|
this.authorizedClientService = authorizedClientService;
|
||||||
|
this.authenticationManager = authenticationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the repository for stored {@link OAuth2AuthorizationRequest}'s.
|
||||||
|
*
|
||||||
|
* @param authorizationRequestRepository the repository for stored {@link OAuth2AuthorizationRequest}'s
|
||||||
|
*/
|
||||||
|
public final void setAuthorizationRequestRepository(AuthorizationRequestRepository<OAuth2AuthorizationRequest> 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.shouldProcessAuthorizationResponse(request)) {
|
||||||
|
this.processAuthorizationResponse(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldProcessAuthorizationResponse(HttpServletRequest request) {
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||||
|
if (authorizationRequest == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String requestUrl = UrlUtils.buildFullRequestUrl(request.getScheme(), request.getServerName(),
|
||||||
|
request.getServerPort(), request.getRequestURI(), null);
|
||||||
|
if (requestUrl.equals(authorizationRequest.getRedirectUri()) &&
|
||||||
|
OAuth2AuthorizationResponseUtils.isAuthorizationResponse(request)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAuthorizationResponse(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request);
|
||||||
|
|
||||||
|
String registrationId = (String) authorizationRequest.getAdditionalParameters().get(OAuth2ParameterNames.REGISTRATION_ID);
|
||||||
|
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||||
|
|
||||||
|
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(request);
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
|
||||||
|
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authenticationResult;
|
||||||
|
|
||||||
|
try {
|
||||||
|
authenticationResult = (OAuth2AuthorizationCodeAuthenticationToken)
|
||||||
|
this.authenticationManager.authenticate(authenticationRequest);
|
||||||
|
} catch (OAuth2AuthenticationException ex) {
|
||||||
|
OAuth2Error error = ex.getError();
|
||||||
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||||
|
.fromUriString(authorizationResponse.getRedirectUri())
|
||||||
|
.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
|
||||||
|
if (!StringUtils.isEmpty(error.getDescription())) {
|
||||||
|
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
|
||||||
|
}
|
||||||
|
if (!StringUtils.isEmpty(error.getUri())) {
|
||||||
|
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_URI, error.getUri());
|
||||||
|
}
|
||||||
|
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build().encode().toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||||
|
authenticationResult.getClientRegistration(),
|
||||||
|
currentAuthentication.getName(),
|
||||||
|
authenticationResult.getAccessToken());
|
||||||
|
|
||||||
|
this.authorizedClientService.saveAuthorizedClient(authorizedClient, currentAuthentication);
|
||||||
|
|
||||||
|
String redirectUrl = authorizationResponse.getRedirectUri();
|
||||||
|
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
|
||||||
|
if (savedRequest != null) {
|
||||||
|
redirectUrl = savedRequest.getRedirectUrl();
|
||||||
|
this.requestCache.removeRequest(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
@ -25,6 +26,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||||
import org.springframework.security.web.RedirectStrategy;
|
import org.springframework.security.web.RedirectStrategy;
|
||||||
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
|
import org.springframework.security.web.savedrequest.RequestCache;
|
||||||
|
import org.springframework.security.web.util.ThrowableAnalyzer;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
@ -51,6 +55,17 @@ import java.util.Map;
|
||||||
* response type, and a redirection URI which the authorization server will send the user-agent back to
|
* response type, and a redirection URI which the authorization server will send the user-agent back to
|
||||||
* once access is granted (or denied) by the End-User (Resource Owner).
|
* once access is granted (or denied) by the End-User (Resource Owner).
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* By default, this {@code Filter} responds to authorization requests
|
||||||
|
* at the {@code URI} {@code /oauth2/authorization/{registrationId}}.
|
||||||
|
* The {@code URI} template variable {@code {registrationId}} represents the
|
||||||
|
* {@link ClientRegistration#getRegistrationId() registration identifier} of the client
|
||||||
|
* that is used for initiating the OAuth 2.0 Authorization Request.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE:</b> The default base {@code URI} {@code /oauth2/authorization} may be overridden
|
||||||
|
* via it's constructor {@link #OAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository, String)}.
|
||||||
|
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
|
@ -69,6 +84,8 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||||
*/
|
*/
|
||||||
public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
|
public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
|
||||||
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
|
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
|
||||||
|
private static final String AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME =
|
||||||
|
ClientAuthorizationRequiredException.class.getName() + ".AUTHORIZATION_REQUIRED_EXCEPTION";
|
||||||
private final AntPathRequestMatcher authorizationRequestMatcher;
|
private final AntPathRequestMatcher authorizationRequestMatcher;
|
||||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||||
private final OAuth2AuthorizationRequestUriBuilder authorizationRequestUriBuilder = new OAuth2AuthorizationRequestUriBuilder();
|
private final OAuth2AuthorizationRequestUriBuilder authorizationRequestUriBuilder = new OAuth2AuthorizationRequestUriBuilder();
|
||||||
|
@ -76,6 +93,8 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||||
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
||||||
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
||||||
new HttpSessionOAuth2AuthorizationRequestRepository();
|
new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||||
|
private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
|
||||||
|
private final RequestCache requestCache = new HttpSessionRequestCache();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided parameters.
|
* Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided parameters.
|
||||||
|
@ -125,7 +144,36 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
try {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw ex;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Check to see if we need to handle ClientAuthorizationRequiredException
|
||||||
|
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
|
||||||
|
ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer
|
||||||
|
.getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);
|
||||||
|
if (authzEx != null) {
|
||||||
|
try {
|
||||||
|
request.setAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME, authzEx);
|
||||||
|
this.sendRedirectForAuthorization(request, response, authzEx.getClientRegistrationId());
|
||||||
|
this.requestCache.saveRequest(request, response);
|
||||||
|
} catch (Exception failed) {
|
||||||
|
this.unsuccessfulRedirectForAuthorization(request, response, failed);
|
||||||
|
} finally {
|
||||||
|
request.removeAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex instanceof ServletException) {
|
||||||
|
throw (ServletException) ex;
|
||||||
|
} else if (ex instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) ex;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRequestAuthorization(HttpServletRequest request, HttpServletResponse response) {
|
private boolean shouldRequestAuthorization(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
@ -133,14 +181,25 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response)
|
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
String registrationId = this.authorizationRequestMatcher
|
String registrationId = this.authorizationRequestMatcher
|
||||||
.extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME);
|
.extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME);
|
||||||
|
this.sendRedirectForAuthorization(request, response, registrationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
String registrationId) throws IOException, ServletException {
|
||||||
|
|
||||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||||
if (clientRegistration == null) {
|
if (clientRegistration == null) {
|
||||||
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
|
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
|
||||||
}
|
}
|
||||||
|
this.sendRedirectForAuthorization(request, response, clientRegistration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
ClientRegistration clientRegistration) throws IOException, ServletException {
|
||||||
|
|
||||||
String redirectUriStr = this.expandRedirectUri(request, clientRegistration);
|
String redirectUriStr = this.expandRedirectUri(request, clientRegistration);
|
||||||
|
|
||||||
|
@ -188,6 +247,11 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||||
port = -1; // Removes the port in UriComponentsBuilder
|
port = -1; // Removes the port in UriComponentsBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Supported URI variables -> baseUrl, action, registrationId
|
||||||
|
// Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
|
||||||
|
Map<String, String> uriVariables = new HashMap<>();
|
||||||
|
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
|
||||||
|
|
||||||
String baseUrl = UriComponentsBuilder.newInstance()
|
String baseUrl = UriComponentsBuilder.newInstance()
|
||||||
.scheme(request.getScheme())
|
.scheme(request.getScheme())
|
||||||
.host(request.getServerName())
|
.host(request.getServerName())
|
||||||
|
@ -195,13 +259,40 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||||
.path(request.getContextPath())
|
.path(request.getContextPath())
|
||||||
.build()
|
.build()
|
||||||
.toUriString();
|
.toUriString();
|
||||||
|
|
||||||
Map<String, String> uriVariables = new HashMap<>();
|
|
||||||
uriVariables.put("baseUrl", baseUrl);
|
uriVariables.put("baseUrl", baseUrl);
|
||||||
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
|
|
||||||
|
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||||
|
String loginAction = "login";
|
||||||
|
String authorizeAction = "authorize";
|
||||||
|
String actionParameter = "action";
|
||||||
|
String action;
|
||||||
|
if (request.getAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME) != null) {
|
||||||
|
action = authorizeAction;
|
||||||
|
} else if (request.getParameter(actionParameter) == null) {
|
||||||
|
action = loginAction;
|
||||||
|
} else {
|
||||||
|
String actionValue = request.getParameter(actionParameter);
|
||||||
|
if (loginAction.equalsIgnoreCase(actionValue)) {
|
||||||
|
action = loginAction;
|
||||||
|
} else {
|
||||||
|
action = authorizeAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uriVariables.put("action", action);
|
||||||
|
}
|
||||||
|
|
||||||
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
|
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
|
||||||
.buildAndExpand(uriVariables)
|
.buildAndExpand(uriVariables)
|
||||||
.toUriString();
|
.toUriString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
|
||||||
|
protected void initExtractorMap() {
|
||||||
|
super.initExtractorMap();
|
||||||
|
registerExtractor(ServletException.class, throwable -> {
|
||||||
|
ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
|
||||||
|
return ((ServletException) throwable).getRootCause();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for an OAuth 2.0 Authorization Response.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
* @see OAuth2AuthorizationResponse
|
||||||
|
*/
|
||||||
|
final class OAuth2AuthorizationResponseUtils {
|
||||||
|
|
||||||
|
private OAuth2AuthorizationResponseUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isAuthorizationResponse(HttpServletRequest request) {
|
||||||
|
return isAuthorizationResponseSuccess(request) || isAuthorizationResponseError(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isAuthorizationResponseSuccess(HttpServletRequest request) {
|
||||||
|
return StringUtils.hasText(request.getParameter(OAuth2ParameterNames.CODE)) &&
|
||||||
|
StringUtils.hasText(request.getParameter(OAuth2ParameterNames.STATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isAuthorizationResponseError(HttpServletRequest request) {
|
||||||
|
return StringUtils.hasText(request.getParameter(OAuth2ParameterNames.ERROR)) &&
|
||||||
|
StringUtils.hasText(request.getParameter(OAuth2ParameterNames.STATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
static OAuth2AuthorizationResponse convert(HttpServletRequest request) {
|
||||||
|
String code = request.getParameter(OAuth2ParameterNames.CODE);
|
||||||
|
String errorCode = request.getParameter(OAuth2ParameterNames.ERROR);
|
||||||
|
String state = request.getParameter(OAuth2ParameterNames.STATE);
|
||||||
|
String redirectUri = request.getRequestURL().toString();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(code)) {
|
||||||
|
return OAuth2AuthorizationResponse.success(code)
|
||||||
|
.redirectUri(redirectUri)
|
||||||
|
.state(state)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
String errorDescription = request.getParameter(OAuth2ParameterNames.ERROR_DESCRIPTION);
|
||||||
|
String errorUri = request.getParameter(OAuth2ParameterNames.ERROR_URI);
|
||||||
|
return OAuth2AuthorizationResponse.error(errorCode)
|
||||||
|
.redirectUri(redirectUri)
|
||||||
|
.errorDescription(errorDescription)
|
||||||
|
.errorUri(errorUri)
|
||||||
|
.state(state)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -134,22 +133,21 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
|
||||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||||
throws AuthenticationException, IOException, ServletException {
|
throws AuthenticationException, IOException, ServletException {
|
||||||
|
|
||||||
if (!this.authorizationResponseSuccess(request) && !this.authorizationResponseError(request)) {
|
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(request)) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
|
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request);
|
||||||
if (authorizationRequest == null) {
|
if (authorizationRequest == null) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
|
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
this.authorizationRequestRepository.removeAuthorizationRequest(request);
|
|
||||||
|
|
||||||
String registrationId = (String) authorizationRequest.getAdditionalParameters().get(OAuth2ParameterNames.REGISTRATION_ID);
|
String registrationId = (String) authorizationRequest.getAdditionalParameters().get(OAuth2ParameterNames.REGISTRATION_ID);
|
||||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||||
|
|
||||||
OAuth2AuthorizationResponse authorizationResponse = this.convert(request);
|
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(request);
|
||||||
|
|
||||||
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(
|
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(
|
||||||
clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
|
clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
|
||||||
|
@ -182,37 +180,4 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
|
||||||
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
|
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
|
||||||
this.authorizationRequestRepository = authorizationRequestRepository;
|
this.authorizationRequestRepository = authorizationRequestRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2AuthorizationResponse convert(HttpServletRequest request) {
|
|
||||||
String code = request.getParameter(OAuth2ParameterNames.CODE);
|
|
||||||
String errorCode = request.getParameter(OAuth2ParameterNames.ERROR);
|
|
||||||
String state = request.getParameter(OAuth2ParameterNames.STATE);
|
|
||||||
String redirectUri = request.getRequestURL().toString();
|
|
||||||
|
|
||||||
if (StringUtils.hasText(code)) {
|
|
||||||
return OAuth2AuthorizationResponse.success(code)
|
|
||||||
.redirectUri(redirectUri)
|
|
||||||
.state(state)
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
String errorDescription = request.getParameter(OAuth2ParameterNames.ERROR_DESCRIPTION);
|
|
||||||
String errorUri = request.getParameter(OAuth2ParameterNames.ERROR_URI);
|
|
||||||
return OAuth2AuthorizationResponse.error(errorCode)
|
|
||||||
.redirectUri(redirectUri)
|
|
||||||
.errorDescription(errorDescription)
|
|
||||||
.errorUri(errorUri)
|
|
||||||
.state(state)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean authorizationResponseSuccess(HttpServletRequest request) {
|
|
||||||
return StringUtils.hasText(request.getParameter(OAuth2ParameterNames.CODE)) &&
|
|
||||||
StringUtils.hasText(request.getParameter(OAuth2ParameterNames.STATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean authorizationResponseError(HttpServletRequest request) {
|
|
||||||
return StringUtils.hasText(request.getParameter(OAuth2ParameterNames.ERROR)) &&
|
|
||||||
StringUtils.hasText(request.getParameter(OAuth2ParameterNames.STATE));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2AuthorizationCodeAuthenticationProvider}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
@PrepareForTest({ClientRegistration.class, OAuth2AuthorizationRequest.class,
|
||||||
|
OAuth2AuthorizationResponse.class, OAuth2AccessTokenResponse.class})
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||||
|
private ClientRegistration clientRegistration;
|
||||||
|
private OAuth2AuthorizationRequest authorizationRequest;
|
||||||
|
private OAuth2AuthorizationResponse authorizationResponse;
|
||||||
|
private OAuth2AuthorizationExchange authorizationExchange;
|
||||||
|
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
private OAuth2AuthorizationCodeAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
this.clientRegistration = mock(ClientRegistration.class);
|
||||||
|
this.authorizationRequest = mock(OAuth2AuthorizationRequest.class);
|
||||||
|
this.authorizationResponse = mock(OAuth2AuthorizationResponse.class);
|
||||||
|
this.authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest, this.authorizationResponse);
|
||||||
|
this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
|
||||||
|
this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(this.accessTokenResponseClient);
|
||||||
|
|
||||||
|
when(this.authorizationRequest.getState()).thenReturn("12345");
|
||||||
|
when(this.authorizationResponse.getState()).thenReturn("12345");
|
||||||
|
when(this.authorizationRequest.getRedirectUri()).thenReturn("http://example.com");
|
||||||
|
when(this.authorizationResponse.getRedirectUri()).thenReturn("http://example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAccessTokenResponseClientIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationProvider(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsWhenTypeOAuth2AuthorizationCodeAuthenticationTokenThenReturnTrue() {
|
||||||
|
assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeAuthenticationToken.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenAuthorizationErrorResponseThenThrowOAuth2AuthenticationException() {
|
||||||
|
when(this.authorizationResponse.statusError()).thenReturn(true);
|
||||||
|
when(this.authorizationResponse.getError()).thenReturn(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
this.authenticationProvider.authenticate(
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
this.clientRegistration, this.authorizationExchange));
|
||||||
|
}).isInstanceOf(OAuth2AuthenticationException.class).hasMessageContaining(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenAuthorizationResponseStateNotEqualAuthorizationRequestStateThenThrowOAuth2AuthenticationException() {
|
||||||
|
when(this.authorizationRequest.getState()).thenReturn("12345");
|
||||||
|
when(this.authorizationResponse.getState()).thenReturn("67890");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
this.authenticationProvider.authenticate(
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
this.clientRegistration, this.authorizationExchange));
|
||||||
|
}).isInstanceOf(OAuth2AuthenticationException.class).hasMessageContaining("invalid_state_parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenAuthorizationResponseRedirectUriNotEqualAuthorizationRequestRedirectUriThenThrowOAuth2AuthenticationException() {
|
||||||
|
when(this.authorizationRequest.getRedirectUri()).thenReturn("http://example.com");
|
||||||
|
when(this.authorizationResponse.getRedirectUri()).thenReturn("http://example2.com");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
this.authenticationProvider.authenticate(
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
this.clientRegistration, this.authorizationExchange));
|
||||||
|
}).isInstanceOf(OAuth2AuthenticationException.class).hasMessageContaining("invalid_redirect_uri_parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenAuthorizationSuccessResponseThenExchangedForAccessToken() {
|
||||||
|
OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class);
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = mock(OAuth2AccessTokenResponse.class);
|
||||||
|
when(accessTokenResponse.getAccessToken()).thenReturn(accessToken);
|
||||||
|
when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2AuthorizationCodeAuthenticationToken) this.authenticationProvider.authenticate(
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
||||||
|
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
|
assertThat(authenticationResult.getPrincipal()).isEqualTo(this.clientRegistration.getClientId());
|
||||||
|
assertThat(authenticationResult.getCredentials()).isEqualTo(accessToken.getTokenValue());
|
||||||
|
assertThat(authenticationResult.getAuthorities()).isEqualTo(Collections.emptyList());
|
||||||
|
assertThat(authenticationResult.getClientRegistration()).isEqualTo(this.clientRegistration);
|
||||||
|
assertThat(authenticationResult.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
|
||||||
|
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2AuthorizationCodeAuthenticationToken}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
@PrepareForTest({ClientRegistration.class, OAuth2AuthorizationExchange.class, OAuth2AuthorizationResponse.class})
|
||||||
|
public class OAuth2AuthorizationCodeAuthenticationTokenTests {
|
||||||
|
private ClientRegistration clientRegistration;
|
||||||
|
private OAuth2AuthorizationExchange authorizationExchange;
|
||||||
|
private OAuth2AccessToken accessToken;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
this.clientRegistration = mock(ClientRegistration.class);
|
||||||
|
this.authorizationExchange = mock(OAuth2AuthorizationExchange.class);
|
||||||
|
this.accessToken = mock(OAuth2AccessToken.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorAuthorizationRequestResponseWhenClientRegistrationIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.authorizationExchange))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorAuthorizationRequestResponseWhenAuthorizationExchangeIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorAuthorizationRequestResponseWhenAllParametersProvidedAndValidThenCreated() {
|
||||||
|
OAuth2AuthorizationResponse authorizationResponse = mock(OAuth2AuthorizationResponse.class);
|
||||||
|
when(authorizationResponse.getCode()).thenReturn("code");
|
||||||
|
when(this.authorizationExchange.getAuthorizationResponse()).thenReturn(authorizationResponse);
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, this.authorizationExchange);
|
||||||
|
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientRegistration.getClientId());
|
||||||
|
assertThat(authentication.getCredentials()).isEqualTo(this.authorizationExchange.getAuthorizationResponse().getCode());
|
||||||
|
assertThat(authentication.getAuthorities()).isEqualTo(Collections.emptyList());
|
||||||
|
assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
|
||||||
|
assertThat(authentication.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
|
||||||
|
assertThat(authentication.getAccessToken()).isNull();
|
||||||
|
assertThat(authentication.isAuthenticated()).isEqualTo(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorTokenRequestResponseWhenClientRegistrationIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.authorizationExchange, this.accessToken))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorTokenRequestResponseWhenAuthorizationExchangeIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, null, this.accessToken))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorTokenRequestResponseWhenAccessTokenIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, this.authorizationExchange, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorTokenRequestResponseWhenAllParametersProvidedAndValidThenCreated() {
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
this.clientRegistration, this.authorizationExchange, this.accessToken);
|
||||||
|
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientRegistration.getClientId());
|
||||||
|
assertThat(authentication.getCredentials()).isEqualTo(this.accessToken.getTokenValue());
|
||||||
|
assertThat(authentication.getAuthorities()).isEqualTo(Collections.emptyList());
|
||||||
|
assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
|
||||||
|
assertThat(authentication.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
|
||||||
|
assertThat(authentication.getAccessToken()).isEqualTo(this.accessToken);
|
||||||
|
assertThat(authentication.isAuthenticated()).isEqualTo(true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
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.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
|
import org.springframework.security.web.savedrequest.RequestCache;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2AuthorizationCodeGrantFilter}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
@PowerMockIgnore("javax.security.*")
|
||||||
|
@PrepareForTest({OAuth2AuthorizationRequest.class, OAuth2AuthorizationExchange.class, OAuth2AuthorizationCodeGrantFilter.class})
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
public class OAuth2AuthorizationCodeGrantFilterTests {
|
||||||
|
private ClientRegistration registration1;
|
||||||
|
private String principalName1 = "principal-1";
|
||||||
|
private ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
private OAuth2AuthorizedClientService authorizedClientService;
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
|
||||||
|
private OAuth2AuthorizationCodeGrantFilter filter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
this.registration1 = ClientRegistration.withRegistrationId("registration-1")
|
||||||
|
.clientId("client-1")
|
||||||
|
.clientSecret("secret")
|
||||||
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.redirectUriTemplate("{baseUrl}/callback/client-1")
|
||||||
|
.scope("user")
|
||||||
|
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||||
|
.tokenUri("https://provider.com/oauth2/token")
|
||||||
|
.userInfoUri("https://provider.com/oauth2/user")
|
||||||
|
.userNameAttributeName("id")
|
||||||
|
.clientName("client-1")
|
||||||
|
.build();
|
||||||
|
this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1);
|
||||||
|
this.authorizedClientService = new InMemoryOAuth2AuthorizedClientService(this.clientRegistrationRepository);
|
||||||
|
this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||||
|
this.authenticationManager = mock(AuthenticationManager.class);
|
||||||
|
this.filter = spy(new OAuth2AuthorizationCodeGrantFilter(
|
||||||
|
this.clientRegistrationRepository, this.authorizedClientService, this.authenticationManager));
|
||||||
|
this.filter.setAuthorizationRequestRepository(this.authorizationRequestRepository);
|
||||||
|
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||||
|
securityContext.setAuthentication(new TestingAuthenticationToken(this.principalName1, "password"));
|
||||||
|
SecurityContextHolder.setContext(securityContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeGrantFilter(null, this.authorizedClientService, this.authenticationManager))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAuthorizedClientServiceIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeGrantFilter(this.clientRegistrationRepository, null, this.authenticationManager))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAuthenticationManagerIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeGrantFilter(this.clientRegistrationRepository, this.authorizedClientService, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.filter.setAuthorizationRequestRepository(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenNotAuthorizationResponseThenNotProcessed() throws Exception {
|
||||||
|
String requestUri = "/path";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
// NOTE: A valid Authorization Response contains either a 'code' or 'error' parameter.
|
||||||
|
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestNotFoundThenNotProcessed() throws Exception {
|
||||||
|
String requestUri = "/path";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationResponseUrlDoesNotMatchAuthorizationRequestRedirectUriThenNotProcessed() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
|
request.setRequestURI(requestUri + "-no-match");
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationResponseValidThenAuthorizationRequestRemoved() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
assertThat(this.authorizationRequestRepository.loadAuthorizationRequest(request)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthenticationFailsThenHandleOAuth2AuthenticationException() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
|
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT);
|
||||||
|
when(this.authenticationManager.authenticate(any(Authentication.class)))
|
||||||
|
.thenThrow(new OAuth2AuthenticationException(error, error.toString()));
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1?error=invalid_grant");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationResponseSuccessThenAuthorizedClientSaved() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient(
|
||||||
|
this.registration1.getRegistrationId(), this.principalName1);
|
||||||
|
assertThat(authorizedClient).isNotNull();
|
||||||
|
assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
|
||||||
|
assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principalName1);
|
||||||
|
assertThat(authorizedClient.getAccessToken()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationResponseSuccessThenRedirected() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationResponseSuccessHasSavedRequestThenRedirectedToSavedRequest() throws Exception {
|
||||||
|
String requestUri = "/saved-request";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
RequestCache requestCache = new HttpSessionRequestCache();
|
||||||
|
requestCache.saveRequest(request, response);
|
||||||
|
|
||||||
|
requestUri = "/callback/client-1";
|
||||||
|
request.setRequestURI(requestUri);
|
||||||
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/saved-request");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
ClientRegistration registration) {
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId());
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = mock(OAuth2AuthorizationRequest.class);
|
||||||
|
when(authorizationRequest.getAdditionalParameters()).thenReturn(additionalParameters);
|
||||||
|
when(authorizationRequest.getRedirectUri()).thenReturn(request.getRequestURL().toString());
|
||||||
|
when(authorizationRequest.getState()).thenReturn("state");
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpAuthenticationResult(ClientRegistration registration) {
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication = mock(OAuth2AuthorizationCodeAuthenticationToken.class);
|
||||||
|
when(authentication.getClientRegistration()).thenReturn(registration);
|
||||||
|
when(authentication.getAuthorizationExchange()).thenReturn(mock(OAuth2AuthorizationExchange.class));
|
||||||
|
when(authentication.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
|
||||||
|
when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,18 +21,25 @@ import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,7 +61,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
.clientSecret("secret")
|
.clientSecret("secret")
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
|
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
|
||||||
.scope("user")
|
.scope("user")
|
||||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||||
.tokenUri("https://provider.com/oauth2/token")
|
.tokenUri("https://provider.com/oauth2/token")
|
||||||
|
@ -67,7 +74,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
.clientSecret("secret")
|
.clientSecret("secret")
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
|
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
|
||||||
.scope("openid", "profile", "email")
|
.scope("openid", "profile", "email")
|
||||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||||
.tokenUri("https://provider.com/oauth2/token")
|
.tokenUri("https://provider.com/oauth2/token")
|
||||||
|
@ -78,7 +85,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
this.registration3 = ClientRegistration.withRegistrationId("registration-3")
|
this.registration3 = ClientRegistration.withRegistrationId("registration-3")
|
||||||
.clientId("client-3")
|
.clientId("client-3")
|
||||||
.authorizationGrantType(AuthorizationGrantType.IMPLICIT)
|
.authorizationGrantType(AuthorizationGrantType.IMPLICIT)
|
||||||
.redirectUriTemplate("{baseUrl}/login/oauth2/implicit/{registrationId}")
|
.redirectUriTemplate("{baseUrl}/authorize/oauth2/implicit/{registrationId}")
|
||||||
.scope("openid", "profile", "email")
|
.scope("openid", "profile", "email")
|
||||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||||
.tokenUri("https://provider.com/oauth2/token")
|
.tokenUri("https://provider.com/oauth2/token")
|
||||||
|
@ -90,19 +97,22 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
this.filter = new OAuth2AuthorizationRequestRedirectFilter(this.clientRegistrationRepository);
|
this.filter = new OAuth2AuthorizationRequestRedirectFilter(this.clientRegistrationRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
|
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||||
new OAuth2AuthorizationRequestRedirectFilter(null);
|
assertThatThrownBy(() -> new OAuth2AuthorizationRequestRedirectFilter(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void constructorWhenAuthorizationRequestBaseUriIsNullThenThrowIllegalArgumentException() {
|
public void constructorWhenAuthorizationRequestBaseUriIsNullThenThrowIllegalArgumentException() {
|
||||||
new OAuth2AuthorizationRequestRedirectFilter(this.clientRegistrationRepository, null);
|
assertThatThrownBy(() -> new OAuth2AuthorizationRequestRedirectFilter(this.clientRegistrationRepository, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryIsNullThenThrowIllegalArgumentException() {
|
public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||||
this.filter.setAuthorizationRequestRepository(null);
|
assertThatThrownBy(() -> this.filter.setAuthorizationRequestRepository(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -136,7 +146,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationRequestAuthorizationCodeGrantThenRedirectForAuthorization() throws Exception {
|
public void doFilterWhenAuthorizationRequestOAuth2LoginThenRedirectForAuthorization() throws Exception {
|
||||||
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
||||||
"/" + this.registration1.getRegistrationId();
|
"/" + this.registration1.getRegistrationId();
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
@ -152,7 +162,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationRequestAuthorizationCodeGrantThenAuthorizationRequestSaved() throws Exception {
|
public void doFilterWhenAuthorizationRequestOAuth2LoginThenAuthorizationRequestSaved() throws Exception {
|
||||||
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
||||||
"/" + this.registration2.getRegistrationId();
|
"/" + this.registration2.getRegistrationId();
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
@ -184,7 +194,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
|
|
||||||
verifyZeroInteractions(filterChain);
|
verifyZeroInteractions(filterChain);
|
||||||
|
|
||||||
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=token&client_id=client-3&scope=openid%20profile%20email&state=.{15,}&redirect_uri=http://localhost/login/oauth2/implicit/registration-3");
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=token&client_id=client-3&scope=openid%20profile%20email&state=.{15,}&redirect_uri=http://localhost/authorize/oauth2/implicit/registration-3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -292,4 +302,101 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||||
|
|
||||||
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=https://example.com/login/oauth2/code/registration-1");
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=https://example.com/login/oauth2/code/registration-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenNotAuthorizationRequestAndClientAuthorizationRequiredExceptionThrownThenRedirectForAuthorization() throws Exception {
|
||||||
|
String requestUri = "/path";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
doThrow(new ClientAuthorizationRequiredException(this.registration1.getRegistrationId()))
|
||||||
|
.when(filterChain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http://localhost/authorize/oauth2/code/registration-1");
|
||||||
|
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
assertThat(session).isNotNull();
|
||||||
|
boolean requestSaved = false;
|
||||||
|
for (String attrName : Collections.list(session.getAttributeNames())) {
|
||||||
|
if (SavedRequest.class.isAssignableFrom(session.getAttribute(attrName).getClass())) {
|
||||||
|
requestSaved = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(requestSaved).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenNotAuthorizationRequestAndClientAuthorizationRequiredExceptionThrownThenRedirectUriIsAuthorize() throws Exception {
|
||||||
|
String requestUri = "/path";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
doThrow(new ClientAuthorizationRequiredException(this.registration1.getRegistrationId()))
|
||||||
|
.when(filterChain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http://localhost/authorize/oauth2/code/registration-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestOAuth2LoginThenRedirectUriIsLogin() throws Exception {
|
||||||
|
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
||||||
|
"/" + this.registration2.getRegistrationId();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verifyZeroInteractions(filterChain);
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-2&scope=openid%20profile%20email&state=.{15,}&redirect_uri=http://localhost/login/oauth2/code/registration-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestHasActionParameterAuthorizeThenRedirectUriIsAuthorize() throws Exception {
|
||||||
|
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
||||||
|
"/" + this.registration1.getRegistrationId();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.addParameter("action", "authorize");
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verifyZeroInteractions(filterChain);
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http://localhost/authorize/oauth2/code/registration-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestHasActionParameterLoginThenRedirectUriIsLogin() throws Exception {
|
||||||
|
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
|
||||||
|
"/" + this.registration2.getRegistrationId();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.addParameter("action", "login");
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verifyZeroInteractions(filterChain);
|
||||||
|
|
||||||
|
assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-2&scope=openid%20profile%20email&state=.{15,}&redirect_uri=http://localhost/login/oauth2/code/registration-2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.powermock.api.mockito.PowerMockito;
|
|
||||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
@ -54,9 +53,8 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2LoginAuthenticationFilter}.
|
* Tests for {@link OAuth2LoginAuthenticationFilter}.
|
||||||
|
@ -118,24 +116,28 @@ public class OAuth2LoginAuthenticationFilterTests {
|
||||||
this.filter.setAuthenticationManager(this.authenticationManager);
|
this.filter.setAuthenticationManager(this.authenticationManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
|
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||||
new OAuth2LoginAuthenticationFilter(null, this.authorizedClientService);
|
assertThatThrownBy(() -> new OAuth2LoginAuthenticationFilter(null, this.authorizedClientService))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void constructorWhenAuthorizedClientServiceIsNullThenThrowIllegalArgumentException() {
|
public void constructorWhenAuthorizedClientServiceIsNullThenThrowIllegalArgumentException() {
|
||||||
new OAuth2LoginAuthenticationFilter(this.clientRegistrationRepository, null);
|
assertThatThrownBy(() -> new OAuth2LoginAuthenticationFilter(this.clientRegistrationRepository, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void constructorWhenFilterProcessesUrlIsNullThenThrowIllegalArgumentException() {
|
public void constructorWhenFilterProcessesUrlIsNullThenThrowIllegalArgumentException() {
|
||||||
new OAuth2LoginAuthenticationFilter(this.clientRegistrationRepository, this.authorizedClientService, null);
|
assertThatThrownBy(() -> new OAuth2LoginAuthenticationFilter(this.clientRegistrationRepository, this.authorizedClientService, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryIsNullThenThrowIllegalArgumentException() {
|
public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryIsNullThenThrowIllegalArgumentException() {
|
||||||
this.filter.setAuthorizationRequestRepository(null);
|
assertThatThrownBy(() -> this.filter.setAuthorizationRequestRepository(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -268,25 +270,6 @@ public class OAuth2LoginAuthenticationFilterTests {
|
||||||
verify(this.filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
verify(this.filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void attemptAuthenticationWhenAuthorizationRequestIsNullThenAuthorizationResponseNotCreated() throws Exception {
|
|
||||||
OAuth2LoginAuthenticationFilter filter = PowerMockito.spy(new OAuth2LoginAuthenticationFilter(
|
|
||||||
this.clientRegistrationRepository, this.authorizedClientService));
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
try {
|
|
||||||
filter.attemptAuthentication(request, response);
|
|
||||||
fail();
|
|
||||||
} catch (OAuth2AuthenticationException ex) {
|
|
||||||
verifyPrivate(filter, never()).invoke("convert", any(HttpServletRequest.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
|
private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
|
||||||
ClientRegistration registration, String state) {
|
ClientRegistration registration, String state) {
|
||||||
OAuth2AuthorizationRequest authorizationRequest = mock(OAuth2AuthorizationRequest.class);
|
OAuth2AuthorizationRequest authorizationRequest = mock(OAuth2AuthorizationRequest.class);
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
= OAuth 2.0 Authorization Code Grant Sample
|
||||||
|
|
||||||
|
== GitHub Repositories
|
||||||
|
|
||||||
|
This guide provides instructions on setting up the sample application, which leverages the OAuth 2.0 Authorization Code Grant, and displays a list of public GitHub repositories that are accessible to the authenticated user.
|
||||||
|
|
||||||
|
This includes repositories owned by the authenticated user, repositories where the authenticated user is a collaborator, and repositories that the authenticated user has access to through an organization membership.
|
||||||
|
|
||||||
|
The following sections provide detailed steps for setting up the sample and covers the following topics:
|
||||||
|
|
||||||
|
* <<github-register-application,Register OAuth application>>
|
||||||
|
* <<github-application-config,Configure application.yml>>
|
||||||
|
* <<github-boot-application,Boot up the application>>
|
||||||
|
|
||||||
|
[[github-register-application]]
|
||||||
|
=== Register OAuth application
|
||||||
|
|
||||||
|
To use GitHub's OAuth 2.0 authorization system, you must https://github.com/settings/applications/new[Register a new OAuth application].
|
||||||
|
|
||||||
|
When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/github-repos`.
|
||||||
|
|
||||||
|
The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub and have granted access to the OAuth application on the _Authorize application_ page.
|
||||||
|
|
||||||
|
[[github-application-config]]
|
||||||
|
=== Configure application.yml
|
||||||
|
|
||||||
|
Now that you have a new OAuth application with GitHub, you need to configure the sample to use the OAuth application for the _authorization code grant flow_.
|
||||||
|
To do so:
|
||||||
|
|
||||||
|
. Go to `application.yml` and set the following configuration:
|
||||||
|
+
|
||||||
|
[source,yaml]
|
||||||
|
----
|
||||||
|
spring:
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
client:
|
||||||
|
registration: <1>
|
||||||
|
github: <2>
|
||||||
|
client-id: github-client-id
|
||||||
|
client-secret: github-client-secret
|
||||||
|
scope: public_repo
|
||||||
|
redirect-uri-template: "{baseUrl}/github-repos"
|
||||||
|
client-name: GitHub Repositories
|
||||||
|
----
|
||||||
|
+
|
||||||
|
.OAuth Client properties
|
||||||
|
====
|
||||||
|
<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties.
|
||||||
|
<2> Following the base property prefix is the ID for the `ClientRegistration`, which is github.
|
||||||
|
====
|
||||||
|
|
||||||
|
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||||
|
|
||||||
|
[[github-boot-application]]
|
||||||
|
=== Boot up the application
|
||||||
|
|
||||||
|
Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
|
||||||
|
You are then redirected to the default _auto-generated_ form login page.
|
||||||
|
Log in using *'user'* (username) and *'password'* (password) and then you'll be redirected to GitHub for authentication.
|
||||||
|
|
||||||
|
After authenticating with your GitHub credentials, the next page presented to you is "Authorize application".
|
||||||
|
This page will ask you to *Authorize* the application you created in the previous step.
|
||||||
|
Click _Authorize application_ to allow the OAuth application to access and display your public repository information.
|
|
@ -0,0 +1,16 @@
|
||||||
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
|
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':spring-security-config')
|
||||||
|
compile project(':spring-security-oauth2-client')
|
||||||
|
compile 'org.springframework:spring-webflux'
|
||||||
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
|
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
|
||||||
|
compile 'io.projectreactor.ipc:reactor-netty'
|
||||||
|
|
||||||
|
testCompile project(':spring-security-test')
|
||||||
|
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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.samples;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.SpringBootConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
|
||||||
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for the OAuth 2.0 client filters {@link OAuth2AuthorizationRequestRedirectFilter}
|
||||||
|
* and {@link OAuth2AuthorizationCodeGrantFilter}. These filters work together to realize
|
||||||
|
* the OAuth 2.0 Authorization Code Grant flow.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
public class OAuth2AuthorizationCodeGrantApplicationTests {
|
||||||
|
@Autowired
|
||||||
|
private ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OAuth2AuthorizedClientService authorizedClientService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestWhenClientNotAuthorizedThenRedirectForAuthorization() throws Exception {
|
||||||
|
MvcResult mvcResult = this.mockMvc.perform(get("/repos").with(user("user")))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andReturn();
|
||||||
|
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://github.com/login/oauth/authorize\\?response_type=code&client_id=your-app-client-id&scope=public_repo&state=.{15,}&redirect_uri=http://localhost/github-repos");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void requestWhenClientGrantedAuthorizationThenAuthorizedClientSaved() throws Exception {
|
||||||
|
// Setup the Authorization Request in the session
|
||||||
|
ClientRegistration registration = this.clientRegistrationRepository.findByRegistrationId("github");
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId());
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||||
|
.authorizationUri(registration.getProviderDetails().getAuthorizationUri())
|
||||||
|
.clientId(registration.getClientId())
|
||||||
|
.redirectUri("http://localhost/github-repos")
|
||||||
|
.scopes(registration.getScopes())
|
||||||
|
.state("state")
|
||||||
|
.additionalParameters(additionalParameters)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
||||||
|
new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
||||||
|
|
||||||
|
MockHttpSession session = (MockHttpSession) request.getSession();
|
||||||
|
|
||||||
|
String principalName = "user";
|
||||||
|
|
||||||
|
// Authorization Response
|
||||||
|
this.mockMvc.perform(get("/github-repos")
|
||||||
|
.param(OAuth2ParameterNames.CODE, "code")
|
||||||
|
.param(OAuth2ParameterNames.STATE, "state")
|
||||||
|
.with(user(principalName))
|
||||||
|
.session(session))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("http://localhost/github-repos"));
|
||||||
|
|
||||||
|
OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient(
|
||||||
|
registration.getRegistrationId(), principalName);
|
||||||
|
assertThat(authorizedClient).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
// @formatter:off
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.oauth2()
|
||||||
|
.client()
|
||||||
|
.authorizationCodeGrant()
|
||||||
|
.tokenEndpoint()
|
||||||
|
.accessTokenResponseClient(this.accessTokenResponseClient());
|
||||||
|
}
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
|
||||||
|
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||||
|
.expiresIn(60 * 1000)
|
||||||
|
.build();
|
||||||
|
OAuth2AccessTokenResponseClient tokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
|
||||||
|
when(tokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
|
||||||
|
return tokenResponseClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootConfiguration
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@ComponentScan(basePackages = "sample.web")
|
||||||
|
public static class SpringBootApplicationTestConfig {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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 sample;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class OAuth2AuthorizationCodeGrantApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(OAuth2AuthorizationCodeGrantApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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 sample.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.formLogin()
|
||||||
|
.and()
|
||||||
|
.oauth2()
|
||||||
|
.client()
|
||||||
|
.authorizationCodeGrant();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService() {
|
||||||
|
UserDetails userDetails = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build();
|
||||||
|
return new InMemoryUserDetailsManager(userDetails);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2018 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 sample.web;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||||
|
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
public class MainController {
|
||||||
|
@Autowired
|
||||||
|
private OAuth2AuthorizedClientService authorizedClientService;
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "redirect:/repos";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/repos")
|
||||||
|
public String gitHubRepos(Model model, Authentication authentication) {
|
||||||
|
String registrationId = "github";
|
||||||
|
|
||||||
|
OAuth2AuthorizedClient authorizedClient =
|
||||||
|
this.authorizedClientService.loadAuthorizedClient(
|
||||||
|
registrationId, authentication.getName());
|
||||||
|
if (authorizedClient == null) {
|
||||||
|
throw new ClientAuthorizationRequiredException(registrationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
String endpointUri = "https://api.github.com/user/repos";
|
||||||
|
List repos = WebClient.builder()
|
||||||
|
.filter(oauth2Credentials(authorizedClient))
|
||||||
|
.build()
|
||||||
|
.get()
|
||||||
|
.uri(endpointUri)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(List.class)
|
||||||
|
.block();
|
||||||
|
model.addAttribute("repos", repos);
|
||||||
|
|
||||||
|
return "github-repos";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExchangeFilterFunction oauth2Credentials(OAuth2AuthorizedClient authorizedClient) {
|
||||||
|
return ExchangeFilterFunction.ofRequestProcessor(
|
||||||
|
clientRequest -> {
|
||||||
|
ClientRequest authorizedRequest = ClientRequest.from(clientRequest)
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authorizedClient.getAccessToken().getTokenValue())
|
||||||
|
.build();
|
||||||
|
return Mono.just(authorizedRequest);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
|
org.springframework.web: INFO
|
||||||
|
org.springframework.security: INFO
|
||||||
|
# org.springframework.boot.autoconfigure: DEBUG
|
||||||
|
|
||||||
|
spring:
|
||||||
|
thymeleaf:
|
||||||
|
cache: false
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
client:
|
||||||
|
registration:
|
||||||
|
github:
|
||||||
|
client-id: your-app-client-id
|
||||||
|
client-secret: your-app-client-secret
|
||||||
|
scope: public_repo
|
||||||
|
redirect-uri-template: "{baseUrl}/github-repos"
|
||||||
|
client-name: GitHub Repositories
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
|
||||||
|
<head>
|
||||||
|
<title>Spring Security - OAuth 2.0 Authorization Code Grant</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
|
||||||
|
<div style="float:left">
|
||||||
|
<span style="font-weight:bold">User: </span><span sec:authentication="name"></span>
|
||||||
|
</div>
|
||||||
|
<div style="float:none"> </div>
|
||||||
|
<div style="float:right">
|
||||||
|
<form action="#" th:action="@{/logout}" method="post">
|
||||||
|
<input type="submit" value="Logout" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1>GitHub Repositories</h1>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li th:each="repo : ${repos}">
|
||||||
|
<span style="font-weight:bold" th:text="${repo.name}"></span>: <span th:text="${repo.url}"></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue