From d840090cb07bf07361434510644ad9c8347945cb Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 11 Oct 2017 10:51:44 -0400 Subject: [PATCH] Add support for implicit grant type Fixes gh-4500 --- .../web/builders/FilterComparator.java | 2 +- .../AuthorizationCodeGrantConfigurer.java | 12 +-- .../client/ImplicitGrantConfigurer.java | 84 +++++++++++++++++++ .../client/CommonOAuth2ProviderTests.java | 5 +- .../registration/ClientRegistration.java | 35 ++++++-- ...AuthorizationCodeAuthenticationFilter.java | 6 +- ...> AuthorizationRequestRedirectFilter.java} | 61 ++++++++------ .../web/AuthorizationRequestRepository.java | 2 +- ...DefaultAuthorizationRequestUriBuilder.java | 17 ++-- ...horizationRequestRedirectFilterTests.java} | 22 ++--- .../security/oauth2/client/web/TestUtil.java | 3 +- .../oauth2/core/AuthorizationGrantType.java | 1 + .../core/endpoint/AuthorizationRequest.java | 11 ++- .../oauth2/core/endpoint/ResponseType.java | 6 +- .../samples/OAuth2LoginApplicationTests.java | 6 +- 15 files changed, 197 insertions(+), 76 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/ImplicitGrantConfigurer.java rename oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/{AuthorizationCodeRequestRedirectFilter.java => AuthorizationRequestRedirectFilter.java} (72%) rename oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/{AuthorizationCodeRequestRedirectFilterTests.java => AuthorizationRequestRedirectFilterTests.java} (87%) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java index a6f6482a94..e48e598d8e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java @@ -78,7 +78,7 @@ final class FilterComparator implements Comparator, Serializable { put(LogoutFilter.class, order); order += STEP; filterToOrder.put( - "org.springframework.security.oauth2.client.web.AuthorizationCodeRequestRedirectFilter", + "org.springframework.security.oauth2.client.web.AuthorizationRequestRedirectFilter", order); order += STEP; put(X509AuthenticationFilter.class, order); diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java index 0bc5bdfeb1..20ed91a6f8 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeGrantConfigurer.java @@ -35,7 +35,7 @@ import org.springframework.security.oauth2.client.user.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.user.DelegatingOAuth2UserService; import org.springframework.security.oauth2.client.user.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationFilter; -import org.springframework.security.oauth2.client.web.AuthorizationCodeRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRedirectFilter; import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestUriBuilder; @@ -63,7 +63,7 @@ public class AuthorizationCodeGrantConfigurer> AbstractHttpConfigurer, B> { // ***** Authorization Request members - private AuthorizationCodeRequestRedirectFilter authorizationRequestFilter; + private AuthorizationRequestRedirectFilter authorizationRequestFilter; private String authorizationRequestBaseUri; private AuthorizationRequestUriBuilder authorizationRequestBuilder; private AuthorizationRequestRepository authorizationRequestRepository; @@ -180,8 +180,8 @@ public class AuthorizationCodeGrantConfigurer> // ************************* // ***** Initialize Filter's // - // -> AuthorizationCodeRequestRedirectFilter - this.authorizationRequestFilter = new AuthorizationCodeRequestRedirectFilter( + // -> AuthorizationRequestRedirectFilter + this.authorizationRequestFilter = new AuthorizationRequestRedirectFilter( this.getAuthorizationRequestBaseUri(), this.getClientRegistrationRepository()); if (this.authorizationRequestBuilder != null) { this.authorizationRequestFilter.setAuthorizationUriBuilder(this.authorizationRequestBuilder); @@ -210,14 +210,14 @@ public class AuthorizationCodeGrantConfigurer> http.addFilter(this.postProcess(this.authorizationResponseFilter)); } - AuthorizationCodeRequestRedirectFilter getAuthorizationRequestFilter() { + AuthorizationRequestRedirectFilter getAuthorizationRequestFilter() { return this.authorizationRequestFilter; } String getAuthorizationRequestBaseUri() { return this.authorizationRequestBaseUri != null ? this.authorizationRequestBaseUri : - AuthorizationCodeRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; + AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; } String getAuthorizationResponseBaseUri() { diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/ImplicitGrantConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/ImplicitGrantConfigurer.java new file mode 100644 index 0000000000..6f5352efb6 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/ImplicitGrantConfigurer.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.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.configurers.AbstractHttpConfigurer; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.AuthorizationRequestUriBuilder; +import org.springframework.util.Assert; + +/** + * A security configurer for the Implicit Grant type. + * + * @author Joe Grandja + * @since 5.0 + */ +public final class ImplicitGrantConfigurer> extends + AbstractHttpConfigurer, B> { + + private String authorizationRequestBaseUri; + private AuthorizationRequestUriBuilder authorizationRequestBuilder; + + public ImplicitGrantConfigurer authorizationRequestBaseUri(String authorizationRequestBaseUri) { + Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty"); + this.authorizationRequestBaseUri = authorizationRequestBaseUri; + return this; + } + + public ImplicitGrantConfigurer authorizationRequestBuilder(AuthorizationRequestUriBuilder authorizationRequestBuilder) { + Assert.notNull(authorizationRequestBuilder, "authorizationRequestBuilder cannot be null"); + this.authorizationRequestBuilder = authorizationRequestBuilder; + return this; + } + + public ImplicitGrantConfigurer clientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) { + Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); + this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository); + return this; + } + + @Override + public void configure(B http) throws Exception { + AuthorizationRequestRedirectFilter authorizationRequestFilter = new AuthorizationRequestRedirectFilter( + this.getAuthorizationRequestBaseUri(), this.getClientRegistrationRepository()); + if (this.authorizationRequestBuilder != null) { + authorizationRequestFilter.setAuthorizationUriBuilder(this.authorizationRequestBuilder); + } + http.addFilter(this.postProcess(authorizationRequestFilter)); + } + + private String getAuthorizationRequestBaseUri() { + return this.authorizationRequestBaseUri != null ? + this.authorizationRequestBaseUri : + AuthorizationRequestRedirectFilter.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); + } +} diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java index 1c135e8366..5074b07f48 100644 --- a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java +++ b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java @@ -109,7 +109,8 @@ public class CommonOAuth2ProviderTests { ClientRegistration registration = builder(CommonOAuth2Provider.OKTA) .authorizationUri("http://example.com/auth") .tokenUri("http://example.com/token") - .userInfoUri("http://example.com/info").build(); + .userInfoUri("http://example.com/info") + .jwkSetUri("http://example.com/jwkset").build(); ProviderDetails providerDetails = registration.getProviderDetails(); assertThat(providerDetails.getAuthorizationUri()) .isEqualTo("http://example.com/auth"); @@ -117,7 +118,7 @@ public class CommonOAuth2ProviderTests { assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("http://example.com/info"); assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()) .isEqualTo(IdTokenClaim.SUB); - assertThat(providerDetails.getJwkSetUri()).isNull(); + assertThat(providerDetails.getJwkSetUri()).isEqualTo("http://example.com/jwkset"); assertThat(registration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.BASIC); assertThat(registration.getAuthorizationGrantType()) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index 25b84b211e..6758f04e44 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -304,13 +304,18 @@ public class ClientRegistration { } public ClientRegistration build() { - this.validateClientWithAuthorizationCodeGrantType(); - ClientRegistration clientRegistration = new ClientRegistration(); - this.setProperties(clientRegistration); - return clientRegistration; + Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null"); + if (AuthorizationGrantType.IMPLICIT.equals(this.authorizationGrantType)) { + this.validateImplicitGrantType(); + } else { + this.validateAuthorizationCodeGrantType(); + } + return this.create(); } - protected void setProperties(ClientRegistration clientRegistration) { + protected ClientRegistration create() { + ClientRegistration clientRegistration = new ClientRegistration(); + clientRegistration.setRegistrationId(this.registrationId); clientRegistration.setClientId(this.clientId); clientRegistration.setClientSecret(this.clientSecret); @@ -328,9 +333,11 @@ public class ClientRegistration { clientRegistration.setProviderDetails(providerDetails); clientRegistration.setClientName(this.clientName); + + return clientRegistration; } - protected void validateClientWithAuthorizationCodeGrantType() { + protected void validateAuthorizationCodeGrantType() { Assert.isTrue(AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType), "authorizationGrantType must be " + AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); Assert.hasText(this.registrationId, "registrationId cannot be empty"); @@ -341,12 +348,22 @@ public class ClientRegistration { Assert.notEmpty(this.scope, "scope cannot be empty"); Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty"); Assert.hasText(this.tokenUri, "tokenUri cannot be empty"); - if (!this.scope.contains(OidcScope.OPENID)) { - // userInfoUri is optional for OIDC Clients - Assert.hasText(this.userInfoUri, "userInfoUri cannot be empty"); + if (this.scope.contains(OidcScope.OPENID)) { + // OIDC Clients need to verify/validate the ID Token + Assert.hasText(this.jwkSetUri, "jwkSetUri cannot be empty"); } Assert.hasText(this.clientName, "clientName cannot be empty"); + } + + protected void validateImplicitGrantType() { + Assert.isTrue(AuthorizationGrantType.IMPLICIT.equals(this.authorizationGrantType), + "authorizationGrantType must be " + AuthorizationGrantType.IMPLICIT.getValue()); Assert.hasText(this.registrationId, "registrationId cannot be empty"); + Assert.hasText(this.clientId, "clientId cannot be empty"); + Assert.hasText(this.redirectUri, "redirectUri cannot be empty"); + Assert.notEmpty(this.scope, "scope cannot be empty"); + Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty"); + Assert.hasText(this.clientName, "clientName cannot be empty"); } } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilter.java index c68f767eb7..c6728d94db 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilter.java @@ -72,11 +72,11 @@ import java.io.IOException; * @see AbstractAuthenticationProcessingFilter * @see AuthorizationCodeAuthenticationToken * @see AuthorizationCodeAuthenticationProvider - * @see AuthorizationCodeRequestRedirectFilter + * @see AuthorizationRequestRedirectFilter * @see AuthorizationRequest * @see AuthorizationRequestRepository * @see ClientRegistrationRepository - * @see Section 4.1 Authorization Code Grant Flow + * @see Section 4.1 Authorization Code Grant * @see Section 4.1.2 Authorization Response */ public class AuthorizationCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { @@ -121,7 +121,7 @@ public class AuthorizationCodeAuthenticationFilter extends AbstractAuthenticatio // The clientRegistration.redirectUri may contain Uri template variables, whether it's configured by // the user or configured by default. In these cases, the redirectUri will be expanded and ultimately changed - // (by AuthorizationCodeRequestRedirectFilter) before setting it in the authorization request. + // (by AuthorizationRequestRedirectFilter) before setting it in the authorization request. // The resulting redirectUri used for the authorization request and saved within the AuthorizationRequestRepository // MUST BE the same one used to complete the authorization code flow. // Therefore, we'll create a copy of the clientRegistration and override the redirectUri diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeRequestRedirectFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java similarity index 72% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeRequestRedirectFilter.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java index 4662902163..5e2d132028 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationCodeRequestRedirectFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java @@ -19,13 +19,12 @@ import org.springframework.http.HttpStatus; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.security.web.util.matcher.RequestVariablesExtractor; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.UriComponentsBuilder; @@ -40,14 +39,14 @@ import java.util.HashMap; import java.util.Map; /** - * This Filter initiates the authorization code grant flow by redirecting - * the end-user's user-agent to the authorization server's Authorization Endpoint. + * This Filter initiates the authorization code grant or implicit grant flow + * by redirecting the end-user's user-agent to the authorization server's Authorization Endpoint. * *

* It uses an {@link AuthorizationRequestUriBuilder} to build the OAuth 2.0 Authorization Request, * which is used as the redirect URI to the Authorization Endpoint. - * The redirect URI will include the client identifier, requested scope(s), state, response type, and a redirection URI - * which the authorization server will send the user-agent back to (handled by {@link AuthorizationCodeAuthenticationFilter}) + * The redirect URI will include the client identifier, requested scope(s), state, + * response type, and a redirection URI which the authorization server will send the user-agent back to * once access is granted (or denied) by the end-user (resource owner). * * @author Joe Grandja @@ -58,24 +57,26 @@ import java.util.Map; * @see ClientRegistration * @see ClientRegistrationRepository * @see AuthorizationCodeAuthenticationFilter - * @see Section 4.1 Authorization Code Grant Flow - * @see Section 4.1.1 Authorization Request + * @see Section 4.1 Authorization Code Grant + * @see Section 4.1.1 Authorization Request (Authorization Code) + * @see Section 4.2 Implicit Grant + * @see Section 4.2.1 Authorization Request (Implicit) */ -public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter { - public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization/code"; +public class AuthorizationRequestRedirectFilter extends OncePerRequestFilter { + public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; public static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; - private final RequestMatcher authorizationRequestMatcher; + private final AntPathRequestMatcher authorizationRequestMatcher; private final ClientRegistrationRepository clientRegistrationRepository; private AuthorizationRequestUriBuilder authorizationUriBuilder = new DefaultAuthorizationRequestUriBuilder(); private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); private final StringKeyGenerator stateGenerator = new DefaultStateGenerator(); private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); - public AuthorizationCodeRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository) { + public AuthorizationRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository) { this(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI, clientRegistrationRepository); } - public AuthorizationCodeRequestRedirectFilter( + public AuthorizationRequestRedirectFilter( String authorizationRequestBaseUri, ClientRegistrationRepository clientRegistrationRepository) { Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty"); @@ -99,11 +100,11 @@ public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - if (this.shouldRequestAuthorizationCode(request, response)) { + if (this.shouldRequestAuthorization(request, response)) { try { - this.sendRedirectForAuthorizationCode(request, response); + this.sendRedirectForAuthorization(request, response); } catch (Exception failed) { - this.unsuccessfulRedirectForAuthorizationCode(request, response, failed); + this.unsuccessfulRedirectForAuthorization(request, response, failed); } return; } @@ -111,15 +112,15 @@ public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter filterChain.doFilter(request, response); } - protected boolean shouldRequestAuthorizationCode(HttpServletRequest request, HttpServletResponse response) { + protected boolean shouldRequestAuthorization(HttpServletRequest request, HttpServletResponse response) { return this.authorizationRequestMatcher.matches(request); } - protected void sendRedirectForAuthorizationCode(HttpServletRequest request, HttpServletResponse response) + protected void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - String registrationId = ((RequestVariablesExtractor)this.authorizationRequestMatcher) - .extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME); + String registrationId = this.authorizationRequestMatcher + .extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); if (clientRegistration == null) { throw new IllegalArgumentException("Invalid Client Identifier (Registration Id): " + registrationId); @@ -130,8 +131,16 @@ public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter Map additionalParameters = new HashMap<>(); additionalParameters.put(OAuth2Parameter.REGISTRATION_ID, clientRegistration.getRegistrationId()); - AuthorizationRequest authorizationRequest = - AuthorizationRequest.authorizationCode() + AuthorizationRequest.Builder builder; + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { + builder = AuthorizationRequest.authorizationCode(); + } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { + builder = AuthorizationRequest.implicit(); + } else { + throw new IllegalArgumentException("Invalid Authorization Grant Type for Client Registration (" + + clientRegistration.getRegistrationId() + "): " + clientRegistration.getAuthorizationGrantType()); + } + AuthorizationRequest authorizationRequest = builder .clientId(clientRegistration.getClientId()) .authorizeUri(clientRegistration.getProviderDetails().getAuthorizationUri()) .redirectUri(redirectUriStr) @@ -140,14 +149,16 @@ public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter .additionalParameters(additionalParameters) .build(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); + } URI redirectUri = this.authorizationUriBuilder.build(authorizationRequest); this.authorizationRedirectStrategy.sendRedirect(request, response, redirectUri.toString()); } - protected void unsuccessfulRedirectForAuthorizationCode(HttpServletRequest request, HttpServletResponse response, - Exception failed) throws IOException, ServletException { + protected void unsuccessfulRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response, + Exception failed) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authorization Request failed: " + failed.toString(), failed); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRepository.java index fd457008c3..9d4f35cdf9 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRepository.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRepository.java @@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletResponse; * of {@link AuthorizationRequest} between requests. * *

- * Used by the {@link AuthorizationCodeRequestRedirectFilter} for persisting the Authorization Request + * Used by the {@link AuthorizationRequestRedirectFilter} for persisting the Authorization Request * before it initiates the authorization code grant flow. * As well, used by the {@link AuthorizationCodeAuthenticationFilter} when resolving * the associated Authorization Request during the handling of the Authorization Response. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java index 45896bc9a3..456f97e7c6 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java @@ -17,7 +17,6 @@ package org.springframework.security.oauth2.client.web; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; -import org.springframework.security.oauth2.core.endpoint.ResponseType; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; @@ -30,23 +29,23 @@ import java.util.stream.Collectors; * @author Joe Grandja * @since 5.0 * @see AuthorizationRequest - * @see Section 4.1.1 Authorization Request + * @see Section 4.1.1 Authorization Code Grant Request + * @see Section 4.2.1 Implicit Grant Request */ public class DefaultAuthorizationRequestUriBuilder implements AuthorizationRequestUriBuilder { @Override public URI build(AuthorizationRequest authorizationRequest) { UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromUriString(authorizationRequest.getAuthorizeUri()) - .queryParam(OAuth2Parameter.RESPONSE_TYPE, ResponseType.CODE.getValue()); + .fromUriString(authorizationRequest.getAuthorizeUri()) + .queryParam(OAuth2Parameter.RESPONSE_TYPE, authorizationRequest.getResponseType().getValue()) + .queryParam(OAuth2Parameter.CLIENT_ID, authorizationRequest.getClientId()) + .queryParam(OAuth2Parameter.SCOPE, + authorizationRequest.getScope().stream().collect(Collectors.joining(" "))) + .queryParam(OAuth2Parameter.STATE, authorizationRequest.getState()); if (authorizationRequest.getRedirectUri() != null) { uriBuilder.queryParam(OAuth2Parameter.REDIRECT_URI, authorizationRequest.getRedirectUri()); } - uriBuilder - .queryParam(OAuth2Parameter.CLIENT_ID, authorizationRequest.getClientId()) - .queryParam(OAuth2Parameter.SCOPE, - authorizationRequest.getScope().stream().collect(Collectors.joining(" "))) - .queryParam(OAuth2Parameter.STATE, authorizationRequest.getState()); return uriBuilder.build().encode().toUri(); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeRequestRedirectFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java similarity index 87% rename from oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeRequestRedirectFilterTests.java rename to oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java index e817f5d6c3..bab7590fa5 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeRequestRedirectFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java @@ -31,22 +31,22 @@ import javax.servlet.http.HttpServletResponse; import java.net.URI; /** - * Tests {@link AuthorizationCodeRequestRedirectFilter}. + * Tests {@link AuthorizationRequestRedirectFilter}. * * @author Joe Grandja */ -public class AuthorizationCodeRequestRedirectFilterTests { +public class AuthorizationRequestRedirectFilterTests { @Test(expected = IllegalArgumentException.class) public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() { - new AuthorizationCodeRequestRedirectFilter(null); + new AuthorizationRequestRedirectFilter(null); } @Test public void doFilterWhenRequestDoesNotMatchClientThenContinueChain() throws Exception { ClientRegistration clientRegistration = TestUtil.googleClientRegistration(); String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString(); - AuthorizationCodeRequestRedirectFilter filter = + AuthorizationRequestRedirectFilter filter = setupFilter(authorizationUri, clientRegistration); String requestURI = "/path"; @@ -64,7 +64,7 @@ public class AuthorizationCodeRequestRedirectFilterTests { public void doFilterWhenRequestMatchesClientThenRedirectForAuthorization() throws Exception { ClientRegistration clientRegistration = TestUtil.googleClientRegistration(); String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString(); - AuthorizationCodeRequestRedirectFilter filter = + AuthorizationRequestRedirectFilter filter = setupFilter(authorizationUri, clientRegistration); String requestUri = TestUtil.AUTHORIZATION_BASE_URI + "/" + clientRegistration.getRegistrationId(); @@ -84,7 +84,7 @@ public class AuthorizationCodeRequestRedirectFilterTests { public void doFilterWhenRequestMatchesClientThenAuthorizationRequestSavedInSession() throws Exception { ClientRegistration clientRegistration = TestUtil.githubClientRegistration(); String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString(); - AuthorizationCodeRequestRedirectFilter filter = + AuthorizationRequestRedirectFilter filter = setupFilter(authorizationUri, clientRegistration); AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); filter.setAuthorizationRequestRepository(authorizationRequestRepository); @@ -113,8 +113,8 @@ public class AuthorizationCodeRequestRedirectFilterTests { Assertions.assertThat(authorizationRequest.getState()).isNotNull(); } - private AuthorizationCodeRequestRedirectFilter setupFilter(String authorizationUri, - ClientRegistration... clientRegistrations) throws Exception { + private AuthorizationRequestRedirectFilter setupFilter(String authorizationUri, + ClientRegistration... clientRegistrations) throws Exception { AuthorizationRequestUriBuilder authorizationUriBuilder = Mockito.mock(AuthorizationRequestUriBuilder.class); URI authorizationURI = new URI(authorizationUri); @@ -123,11 +123,11 @@ public class AuthorizationCodeRequestRedirectFilterTests { return setupFilter(authorizationUriBuilder, clientRegistrations); } - private AuthorizationCodeRequestRedirectFilter setupFilter(AuthorizationRequestUriBuilder authorizationUriBuilder, - ClientRegistration... clientRegistrations) throws Exception { + private AuthorizationRequestRedirectFilter setupFilter(AuthorizationRequestUriBuilder authorizationUriBuilder, + ClientRegistration... clientRegistrations) throws Exception { ClientRegistrationRepository clientRegistrationRepository = TestUtil.clientRegistrationRepository(clientRegistrations); - AuthorizationCodeRequestRedirectFilter filter = new AuthorizationCodeRequestRedirectFilter(clientRegistrationRepository); + AuthorizationRequestRedirectFilter filter = new AuthorizationRequestRedirectFilter(clientRegistrationRepository); filter.setAuthorizationUriBuilder(authorizationUriBuilder); return filter; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/TestUtil.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/TestUtil.java index 626414b75c..6212f520a6 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/TestUtil.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/TestUtil.java @@ -32,7 +32,7 @@ class TestUtil { static final String DEFAULT_SERVER_NAME = "localhost"; static final int DEFAULT_SERVER_PORT = 8080; static final String DEFAULT_SERVER_URL = DEFAULT_SCHEME + "://" + DEFAULT_SERVER_NAME + ":" + DEFAULT_SERVER_PORT; - static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code"; + static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization"; static final String AUTHORIZE_BASE_URI = "/oauth2/authorize/code"; static final String GOOGLE_REGISTRATION_ID = "google"; static final String GITHUB_REGISTRATION_ID = "github"; @@ -55,6 +55,7 @@ class TestUtil { clientRegistrationProperties.setAuthorizationUri("https://accounts.google.com/o/oauth2/auth"); clientRegistrationProperties.setTokenUri("https://accounts.google.com/o/oauth2/token"); clientRegistrationProperties.setUserInfoUri("https://www.googleapis.com/oauth2/v3/userinfo"); + clientRegistrationProperties.setJwkSetUri("https://www.googleapis.com/oauth2/v3/certs"); clientRegistrationProperties.setRedirectUri(redirectUri); clientRegistrationProperties.setScope(Arrays.stream(new String[] {"openid", "email", "profile"}).collect(Collectors.toSet())); return new ClientRegistration.Builder(clientRegistrationProperties).build(); diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java index 95b5d5d0f5..750a3b7d7b 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java @@ -35,6 +35,7 @@ import org.springframework.util.Assert; */ public final class AuthorizationGrantType { public static final AuthorizationGrantType AUTHORIZATION_CODE = new AuthorizationGrantType("authorization_code"); + public static final AuthorizationGrantType IMPLICIT = new AuthorizationGrantType("implicit"); private final String value; public AuthorizationGrantType(String value) { diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java index 2e0ff1bd9b..595455bcfa 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java @@ -86,6 +86,10 @@ public final class AuthorizationRequest implements Serializable { return new Builder(AuthorizationGrantType.AUTHORIZATION_CODE); } + public static Builder implicit() { + return new Builder(AuthorizationGrantType.IMPLICIT); + } + public static class Builder { private final AuthorizationRequest authorizationRequest; @@ -95,6 +99,8 @@ public final class AuthorizationRequest implements Serializable { this.authorizationRequest.authorizationGrantType = authorizationGrantType; if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) { this.authorizationRequest.responseType = ResponseType.CODE; + } else if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) { + this.authorizationRequest.responseType = ResponseType.TOKEN; } } @@ -129,8 +135,11 @@ public final class AuthorizationRequest implements Serializable { } public AuthorizationRequest build() { - Assert.hasText(this.authorizationRequest.clientId, "clientId cannot be empty"); Assert.hasText(this.authorizationRequest.authorizeUri, "authorizeUri cannot be empty"); + Assert.hasText(this.authorizationRequest.clientId, "clientId cannot be empty"); + if (AuthorizationGrantType.IMPLICIT.equals(this.authorizationRequest.authorizationGrantType)) { + Assert.hasText(this.authorizationRequest.redirectUri, "redirectUri cannot be empty"); + } this.authorizationRequest.scope = Collections.unmodifiableSet( CollectionUtils.isEmpty(this.authorizationRequest.scope) ? Collections.emptySet() : new LinkedHashSet<>(this.authorizationRequest.scope)); diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/ResponseType.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/ResponseType.java index 6b42212d40..7f040efa3e 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/ResponseType.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/ResponseType.java @@ -19,22 +19,20 @@ import org.springframework.util.Assert; /** * The response_type parameter is consumed by the authorization endpoint which - * is used by the authorization code grant type and implicit grant type flows. + * is used by the authorization code grant type and implicit grant type. * The client sets the response_type parameter with the desired grant type before initiating the authorization request. * *

* The response_type parameter value may be one of "code" for requesting an authorization code or * "token" for requesting an access token (implicit grant). - *

- * NOTE: "code" is currently the only supported response type. - * * @author Joe Grandja * @since 5.0 * @see Section 3.1.1 Response Type */ public final class ResponseType { public static final ResponseType CODE = new ResponseType("code"); + public static final ResponseType TOKEN = new ResponseType("token"); private final String value; public ResponseType(String value) { diff --git a/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java b/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java index 16b4fe11fd..f733024883 100644 --- a/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java +++ b/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java @@ -41,7 +41,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.user.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationFilter; -import org.springframework.security.oauth2.client.web.AuthorizationCodeRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRedirectFilter; import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; @@ -71,7 +71,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** - * Integration tests for the OAuth 2.0 client filters {@link AuthorizationCodeRequestRedirectFilter} + * Integration tests for the OAuth 2.0 client filters {@link AuthorizationRequestRedirectFilter} * and {@link AuthorizationCodeAuthenticationFilter}. * These filters work together to realize the Authorization Code Grant flow. * @@ -81,7 +81,7 @@ import static org.mockito.Mockito.when; @SpringBootTest @AutoConfigureMockMvc public class OAuth2LoginApplicationTests { - private static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code"; + private static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization"; private static final String AUTHORIZE_BASE_URL = "http://localhost:8080/oauth2/authorize/code"; @Autowired