diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
index dfc5a5f9f4..919305eed0 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
@@ -120,7 +120,7 @@ public class OAuth2ClientConfigurerTests {
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");
+ assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fclient-1");
}
@Test
@@ -168,7 +168,7 @@ public class OAuth2ClientConfigurerTests {
MvcResult mvcResult = this.mockMvc.perform(get("/resource1").with(user("user1")))
.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");
+ assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fclient-1");
verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
new file mode 100644
index 0000000000..1cdf283d1f
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
@@ -0,0 +1,169 @@
+/*
+ * 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.crypto.keygen.Base64StringKeyGenerator;
+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.ClientRegistrationRepository;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.util.UrlUtils;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.util.Assert;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME;
+
+/**
+ * An implementation of an {@link OAuth2AuthorizationRequestResolver} that attempts to
+ * resolve an {@link OAuth2AuthorizationRequest} from the provided {@code HttpServletRequest}
+ * using the default request {@code URI} pattern {@code /oauth2/authorization/{registrationId}}.
+ *
+ *
+ * NOTE: The default base {@code URI} {@code /oauth2/authorization} may be overridden
+ * via it's constructor {@link #DefaultOAuth2AuthorizationRequestResolver(ClientRegistrationRepository, String)}.
+ *
+ * @author Joe Grandja
+ * @since 5.1
+ * @see OAuth2AuthorizationRequestResolver
+ * @see OAuth2AuthorizationRequestRedirectFilter
+ */
+public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
+ private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
+ private final ClientRegistrationRepository clientRegistrationRepository;
+ private final AntPathRequestMatcher authorizationRequestMatcher;
+ private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
+
+ /**
+ * Constructs a {@code DefaultOAuth2AuthorizationRequestResolver} using the provided parameters.
+ *
+ * @param clientRegistrationRepository the repository of client registrations
+ * @param authorizationRequestBaseUri the base {@code URI} used for resolving authorization requests
+ */
+ public DefaultOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository,
+ String authorizationRequestBaseUri) {
+ Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
+ Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
+ this.clientRegistrationRepository = clientRegistrationRepository;
+ this.authorizationRequestMatcher = new AntPathRequestMatcher(
+ authorizationRequestBaseUri + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
+ }
+
+ @Override
+ public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
+ String registrationId = this.resolveRegistrationId(request);
+ if (registrationId == null) {
+ return null;
+ }
+
+ ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
+ if (clientRegistration == null) {
+ throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
+ }
+
+ OAuth2AuthorizationRequest.Builder builder;
+ if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
+ builder = OAuth2AuthorizationRequest.authorizationCode();
+ } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
+ builder = OAuth2AuthorizationRequest.implicit();
+ } else {
+ throw new IllegalArgumentException("Invalid Authorization Grant Type (" +
+ clientRegistration.getAuthorizationGrantType().getValue() +
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
+ }
+
+ String redirectUriAction = this.resolveRedirectUriAction(request, clientRegistration);
+ String redirectUriStr = this.expandRedirectUri(request, clientRegistration, redirectUriAction);
+
+ Map additionalParameters = new HashMap<>();
+ additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
+
+ OAuth2AuthorizationRequest authorizationRequest = builder
+ .clientId(clientRegistration.getClientId())
+ .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
+ .redirectUri(redirectUriStr)
+ .scopes(clientRegistration.getScopes())
+ .state(this.stateGenerator.generateKey())
+ .additionalParameters(additionalParameters)
+ .build();
+
+ return authorizationRequest;
+ }
+
+ private String resolveRegistrationId(HttpServletRequest request) {
+ // Check for ClientAuthorizationRequiredException which may have been set
+ // in the request by OAuth2AuthorizationRequestRedirectFilter
+ ClientAuthorizationRequiredException authzEx =
+ (ClientAuthorizationRequiredException) request.getAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME);
+ if (authzEx != null) {
+ return authzEx.getClientRegistrationId();
+ }
+ if (this.authorizationRequestMatcher.matches(request)) {
+ return this.authorizationRequestMatcher
+ .extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME);
+ }
+ return null;
+ }
+
+ private String resolveRedirectUriAction(HttpServletRequest request, ClientRegistration clientRegistration) {
+ String action = null;
+ if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
+ String loginAction = "login";
+ String authorizeAction = "authorize";
+ String actionParameter = request.getParameter("action");
+ if (request.getAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME) != null) {
+ // Check for ClientAuthorizationRequiredException which may have been set
+ // in the request by OAuth2AuthorizationRequestRedirectFilter
+ action = authorizeAction;
+ } else if (actionParameter == null) {
+ action = loginAction; // Default
+ } else {
+ if (actionParameter.equalsIgnoreCase(loginAction)) {
+ action = loginAction;
+ } else {
+ action = authorizeAction;
+ }
+ }
+ }
+ return action;
+ }
+
+ private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) {
+ // Supported URI variables -> baseUrl, action, registrationId
+ // Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
+ Map uriVariables = new HashMap<>();
+ uriVariables.put("registrationId", clientRegistration.getRegistrationId());
+ String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
+ .replacePath(request.getContextPath())
+ .build()
+ .toUriString();
+ uriVariables.put("baseUrl", baseUrl);
+ if (action != null) {
+ uriVariables.put("action", action);
+ }
+ return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
+ .buildAndExpand(uriVariables)
+ .toUriString();
+ }
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java
index 44fe7270da..ef6c12975d 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java
@@ -16,34 +16,24 @@
package org.springframework.security.oauth2.client.web;
import org.springframework.http.HttpStatus;
-import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
-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.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-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.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.ThrowableAnalyzer;
-import org.springframework.security.web.util.UrlUtils;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
-import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-import java.net.URI;
-import java.util.Base64;
-import java.util.HashMap;
-import java.util.Map;
/**
* This {@code Filter} initiates the authorization code grant or implicit grant flow
@@ -58,19 +48,24 @@ import java.util.Map;
*
*
* By default, this {@code Filter} responds to authorization requests
- * at the {@code URI} {@code /oauth2/authorization/{registrationId}}.
+ * at the {@code URI} {@code /oauth2/authorization/{registrationId}}
+ * using the default {@link OAuth2AuthorizationRequestResolver}.
* 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.
*
*
- * NOTE: The default base {@code URI} {@code /oauth2/authorization} may be overridden
- * via it's constructor {@link #OAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository, String)}.
+ * The default base {@code URI} {@code /oauth2/authorization} may be overridden
+ * via the constructor {@link #OAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository, String)},
+ * or alternatively, an {@code OAuth2AuthorizationRequestResolver} may be provided to the constructor
+ * {@link #OAuth2AuthorizationRequestRedirectFilter(OAuth2AuthorizationRequestResolver)}
+ * to override the resolving of authorization requests.
* @author Joe Grandja
* @author Rob Winch
* @since 5.0
* @see OAuth2AuthorizationRequest
+ * @see OAuth2AuthorizationRequestResolver
* @see AuthorizationRequestRepository
* @see ClientRegistration
* @see ClientRegistrationRepository
@@ -84,18 +79,14 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
* The default base {@code URI} used for authorization requests.
*/
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 AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME =
+ static final String AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME =
ClientAuthorizationRequiredException.class.getName() + ".AUTHORIZATION_REQUIRED_EXCEPTION";
- private final AntPathRequestMatcher authorizationRequestMatcher;
- private final ClientRegistrationRepository clientRegistrationRepository;
- private final OAuth2AuthorizationRequestUriBuilder authorizationRequestUriBuilder = new OAuth2AuthorizationRequestUriBuilder();
+ private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
- private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
+ private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
private AuthorizationRequestRepository authorizationRequestRepository =
new HttpSessionOAuth2AuthorizationRequestRepository();
private RequestCache requestCache = new HttpSessionRequestCache();
- private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
/**
* Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided parameters.
@@ -112,14 +103,23 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
* @param clientRegistrationRepository the repository of client registrations
* @param authorizationRequestBaseUri the base {@code URI} used for authorization requests
*/
- public OAuth2AuthorizationRequestRedirectFilter(
- ClientRegistrationRepository clientRegistrationRepository, String authorizationRequestBaseUri) {
-
- Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
+ public OAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository,
+ String authorizationRequestBaseUri) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
- this.authorizationRequestMatcher = new AntPathRequestMatcher(
- authorizationRequestBaseUri + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
- this.clientRegistrationRepository = clientRegistrationRepository;
+ Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
+ this.authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(
+ clientRegistrationRepository, authorizationRequestBaseUri);
+ }
+
+ /**
+ * Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided parameters.
+ *
+ * @since 5.1
+ * @param authorizationRequestResolver the resolver used for resolving authorization requests
+ */
+ public OAuth2AuthorizationRequestRedirectFilter(OAuth2AuthorizationRequestResolver authorizationRequestResolver) {
+ Assert.notNull(authorizationRequestResolver, "authorizationRequestResolver cannot be null");
+ this.authorizationRequestResolver = authorizationRequestResolver;
}
/**
@@ -147,12 +147,14 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
- if (this.shouldRequestAuthorization(request, response)) {
- try {
- this.sendRedirectForAuthorization(request, response);
- } catch (Exception failed) {
- this.unsuccessfulRedirectForAuthorization(request, response, failed);
+ try {
+ OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
+ if (authorizationRequest != null) {
+ this.sendRedirectForAuthorization(request, response, authorizationRequest);
+ return;
}
+ } catch (Exception failed) {
+ this.unsuccessfulRedirectForAuthorization(request, response, failed);
return;
}
@@ -168,7 +170,11 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
if (authzEx != null) {
try {
request.setAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME, authzEx);
- this.sendRedirectForAuthorization(request, response, authzEx.getClientRegistrationId());
+ OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
+ if (authorizationRequest == null) {
+ throw authzEx;
+ }
+ this.sendRedirectForAuthorization(request, response, authorizationRequest);
this.requestCache.saveRequest(request, response);
} catch (Exception failed) {
this.unsuccessfulRedirectForAuthorization(request, response, failed);
@@ -188,61 +194,13 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
}
}
- private boolean shouldRequestAuthorization(HttpServletRequest request, HttpServletResponse response) {
- return this.authorizationRequestMatcher.matches(request);
- }
-
- private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response)
- throws IOException, ServletException {
-
- String registrationId = this.authorizationRequestMatcher
- .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);
- if (clientRegistration == null) {
- 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);
-
- Map additionalParameters = new HashMap<>();
- additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
-
- OAuth2AuthorizationRequest.Builder builder;
- if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
- builder = OAuth2AuthorizationRequest.authorizationCode();
- } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
- builder = OAuth2AuthorizationRequest.implicit();
- } else {
- throw new IllegalArgumentException("Invalid Authorization Grant Type (" +
- clientRegistration.getAuthorizationGrantType().getValue() +
- ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
- }
- OAuth2AuthorizationRequest authorizationRequest = builder
- .clientId(clientRegistration.getClientId())
- .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
- .redirectUri(redirectUriStr)
- .scopes(clientRegistration.getScopes())
- .state(this.stateGenerator.generateKey())
- .additionalParameters(additionalParameters)
- .build();
+ OAuth2AuthorizationRequest authorizationRequest) throws IOException, ServletException {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
}
-
- URI redirectUri = this.authorizationRequestUriBuilder.build(authorizationRequest);
- this.authorizationRedirectStrategy.sendRedirect(request, response, redirectUri.toString());
+ this.authorizationRedirectStrategy.sendRedirect(request, response, authorizationRequest.getAuthorizationRequestUri());
}
private void unsuccessfulRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
@@ -254,43 +212,6 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
}
- private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) {
- // Supported URI variables -> baseUrl, action, registrationId
- // Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
- Map uriVariables = new HashMap<>();
- uriVariables.put("registrationId", clientRegistration.getRegistrationId());
-
- String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
- .replacePath(request.getContextPath())
- .build()
- .toUriString();
- uriVariables.put("baseUrl", baseUrl);
-
- 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())
- .buildAndExpand(uriVariables)
- .toUriString();
- }
-
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
protected void initExtractorMap() {
super.initExtractorMap();
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectWebFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectWebFilter.java
index d6fcad1fea..b350d5ed58 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectWebFilter.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectWebFilter.java
@@ -87,7 +87,6 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
ClientAuthorizationRequiredException.class.getName() + ".AUTHORIZATION_REQUIRED_EXCEPTION";
private final ServerWebExchangeMatcher authorizationRequestMatcher;
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
- private final OAuth2AuthorizationRequestUriBuilder authorizationRequestUriBuilder = new OAuth2AuthorizationRequestUriBuilder();
private final ServerRedirectStrategy authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
private ReactiveAuthorizationRequestRepository authorizationRequestRepository =
@@ -184,7 +183,9 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
.saveAuthorizationRequest(authorizationRequest, exchange);
}
- URI redirectUri = this.authorizationRequestUriBuilder.build(authorizationRequest);
+ URI redirectUri = UriComponentsBuilder
+ .fromUriString(authorizationRequest.getAuthorizationRequestUri())
+ .build(true).toUri();
return saveAuthorizationRequest
.then(this.authorizationRedirectStrategy.sendRedirect(exchange, redirectUri));
});
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestResolver.java
new file mode 100644
index 0000000000..1e6a9c03e6
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestResolver.java
@@ -0,0 +1,43 @@
+/*
+ * 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.OAuth2AuthorizationRequest;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Implementations of this interface are capable of resolving
+ * an {@link OAuth2AuthorizationRequest} from the provided {@code HttpServletRequest}.
+ * Used by the {@link OAuth2AuthorizationRequestRedirectFilter} for resolving Authorization Requests.
+ *
+ * @author Joe Grandja
+ * @since 5.1
+ * @see OAuth2AuthorizationRequest
+ * @see OAuth2AuthorizationRequestRedirectFilter
+ */
+public interface OAuth2AuthorizationRequestResolver {
+
+ /**
+ * Returns the {@link OAuth2AuthorizationRequest} resolved from
+ * the provided {@code HttpServletRequest} or {@code null} if not available.
+ *
+ * @param request the {@code HttpServletRequest}
+ * @return the resolved {@link OAuth2AuthorizationRequest} or {@code null} if not available
+ */
+ OAuth2AuthorizationRequest resolve(HttpServletRequest request);
+
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestUriBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestUriBuilder.java
deleted file mode 100644
index 3a9635f9e9..0000000000
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestUriBuilder.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2002-2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.security.oauth2.client.web;
-
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-import org.springframework.web.util.UriComponentsBuilder;
-
-import java.net.URI;
-import java.util.Set;
-
-/**
- * A {@code URI} builder for an OAuth 2.0 Authorization Request.
- *
- * @author Joe Grandja
- * @since 5.0
- * @see OAuth2AuthorizationRequest
- * @see Section 4.1.1 Authorization Code Grant Request
- * @see Section 4.2.1 Implicit Grant Request
- */
-class OAuth2AuthorizationRequestUriBuilder {
-
- URI build(OAuth2AuthorizationRequest authorizationRequest) {
- Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
- Set scopes = authorizationRequest.getScopes();
- UriComponentsBuilder uriBuilder = UriComponentsBuilder
- .fromUriString(authorizationRequest.getAuthorizationUri())
- .queryParam(OAuth2ParameterNames.RESPONSE_TYPE, authorizationRequest.getResponseType().getValue())
- .queryParam(OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getClientId())
- .queryParam(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(scopes, " "))
- .queryParam(OAuth2ParameterNames.STATE, authorizationRequest.getState());
- if (authorizationRequest.getRedirectUri() != null) {
- uriBuilder.queryParam(OAuth2ParameterNames.REDIRECT_URI, authorizationRequest.getRedirectUri());
- }
-
- return uriBuilder.build().encode().toUri();
- }
-}
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java
new file mode 100644
index 0000000000..2ff722aa02
--- /dev/null
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java
@@ -0,0 +1,242 @@
+/*
+ * 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.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
+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.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+
+import static org.assertj.core.api.Assertions.*;
+
+/**
+ * Tests for {@link DefaultOAuth2AuthorizationRequestResolver}.
+ *
+ * @author Joe Grandja
+ */
+public class DefaultOAuth2AuthorizationRequestResolverTests {
+ private ClientRegistration registration1;
+ private ClientRegistration registration2;
+ private ClientRegistrationRepository clientRegistrationRepository;
+ private String authorizationRequestBaseUri = "/oauth2/authorization";
+ private DefaultOAuth2AuthorizationRequestResolver resolver;
+
+ @Before
+ public void setUp() {
+ this.registration1 = ClientRegistration.withRegistrationId("registration-1")
+ .clientId("client-1")
+ .clientSecret("secret")
+ .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
+ .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.registration2 = ClientRegistration.withRegistrationId("registration-2")
+ .clientId("client-2")
+ .clientSecret("secret")
+ .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
+ .scope("openid", "profile", "email")
+ .authorizationUri("https://provider.com/oauth2/authorize")
+ .tokenUri("https://provider.com/oauth2/token")
+ .userInfoUri("https://provider.com/oauth2/userinfo")
+ .jwkSetUri("https://provider.com/oauth2/keys")
+ .clientName("client-2")
+ .build();
+ this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(
+ this.registration1, this.registration2);
+ this.resolver = new DefaultOAuth2AuthorizationRequestResolver(
+ this.clientRegistrationRepository, this.authorizationRequestBaseUri);
+ }
+
+ @Test
+ public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
+ assertThatThrownBy(() -> new DefaultOAuth2AuthorizationRequestResolver(null, this.authorizationRequestBaseUri))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void constructorWhenAuthorizationRequestBaseUriIsNullThenThrowIllegalArgumentException() {
+ assertThatThrownBy(() -> new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository, null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void resolveWhenNotAuthorizationRequestThenDoesNotResolve() {
+ String requestUri = "/path";
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest).isNull();
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestWithInvalidClientThenThrowIllegalArgumentException() {
+ ClientRegistration clientRegistration = this.registration1;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId() + "-invalid";
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+
+ assertThatThrownBy(() -> this.resolver.resolve(request))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Invalid Client Registration with Id: " + clientRegistration.getRegistrationId() + "-invalid");
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestWithValidClientThenResolves() {
+ ClientRegistration clientRegistration = this.registration1;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest).isNotNull();
+ assertThat(authorizationRequest.getAuthorizationUri()).isEqualTo(
+ clientRegistration.getProviderDetails().getAuthorizationUri());
+ assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
+ assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.CODE);
+ assertThat(authorizationRequest.getClientId()).isEqualTo(clientRegistration.getClientId());
+ assertThat(authorizationRequest.getRedirectUri())
+ .isEqualTo("http://localhost/login/oauth2/code/" + clientRegistration.getRegistrationId());
+ assertThat(authorizationRequest.getScopes()).isEqualTo(clientRegistration.getScopes());
+ assertThat(authorizationRequest.getState()).isNotNull();
+ assertThat(authorizationRequest.getAdditionalParameters())
+ .containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-1");
+ }
+
+ @Test
+ public void resolveWhenClientAuthorizationRequiredExceptionAvailableThenResolves() {
+ ClientRegistration clientRegistration = this.registration2;
+ String requestUri = "/path";
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+ request.setAttribute(
+ OAuth2AuthorizationRequestRedirectFilter.AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME,
+ new ClientAuthorizationRequiredException(clientRegistration.getRegistrationId()));
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest).isNotNull();
+ assertThat(authorizationRequest.getAdditionalParameters())
+ .containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestRedirectUriTemplatedThenRedirectUriExpanded() {
+ ClientRegistration clientRegistration = this.registration2;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getRedirectUri()).isNotEqualTo(
+ clientRegistration.getRedirectUriTemplate());
+ assertThat(authorizationRequest.getRedirectUri()).isEqualTo(
+ "http://localhost/login/oauth2/code/" + clientRegistration.getRegistrationId());
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestIncludesPort80ThenExpandedRedirectUriExcludesPort() {
+ ClientRegistration clientRegistration = this.registration1;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setScheme("http");
+ request.setServerName("example.com");
+ request.setServerPort(80);
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Fexample.com%2Flogin%2Foauth2%2Fcode%2Fregistration-1");
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestIncludesPort443ThenExpandedRedirectUriExcludesPort() {
+ ClientRegistration clientRegistration = this.registration1;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setScheme("https");
+ request.setServerName("example.com");
+ request.setServerPort(443);
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=https%3A%2F%2Fexample.com%2Flogin%2Foauth2%2Fcode%2Fregistration-1");
+ }
+
+ @Test
+ public void resolveWhenClientAuthorizationRequiredExceptionAvailableThenRedirectUriIsAuthorize() {
+ ClientRegistration clientRegistration = this.registration1;
+ String requestUri = "/path";
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+ request.setAttribute(
+ OAuth2AuthorizationRequestRedirectFilter.AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME,
+ new ClientAuthorizationRequiredException(clientRegistration.getRegistrationId()));
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fregistration-1");
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestOAuth2LoginThenRedirectUriIsLogin() {
+ ClientRegistration clientRegistration = this.registration2;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-2&scope=openid\\+profile\\+email&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-2");
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestHasActionParameterAuthorizeThenRedirectUriIsAuthorize() {
+ ClientRegistration clientRegistration = this.registration1;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.addParameter("action", "authorize");
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fregistration-1");
+ }
+
+ @Test
+ public void resolveWhenAuthorizationRequestHasActionParameterLoginThenRedirectUriIsLogin() {
+ ClientRegistration clientRegistration = this.registration2;
+ String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+ request.addParameter("action", "login");
+ request.setServletPath(requestUri);
+
+ OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-2&scope=openid\\+profile\\+email&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-2");
+ }
+}
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java
index 3412c852d1..b8dadbaf72 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java
@@ -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");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@ package org.springframework.security.oauth2.client.web;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -29,16 +28,22 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Constructor;
+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.*;
+import static org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME;
/**
* Tests for {@link OAuth2AuthorizationRequestRedirectFilter}.
@@ -100,7 +105,9 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
@Test
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() {
- assertThatThrownBy(() -> new OAuth2AuthorizationRequestRedirectFilter(null))
+ Constructor constructor = ClassUtils.getConstructorIfAvailable(
+ OAuth2AuthorizationRequestRedirectFilter.class, ClientRegistrationRepository.class);
+ assertThatThrownBy(() -> constructor.newInstance(null))
.isInstanceOf(IllegalArgumentException.class);
}
@@ -110,6 +117,14 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
.isInstanceOf(IllegalArgumentException.class);
}
+ @Test
+ public void constructorWhenAuthorizationRequestResolverIsNullThenThrowIllegalArgumentException() {
+ Constructor constructor = ClassUtils.getConstructorIfAvailable(
+ OAuth2AuthorizationRequestRedirectFilter.class, OAuth2AuthorizationRequestResolver.class);
+ assertThatThrownBy(() -> constructor.newInstance(null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
@Test
public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryIsNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.filter.setAuthorizationRequestRepository(null))
@@ -165,7 +180,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
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/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=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-1");
}
@Test
@@ -201,7 +216,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
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/authorize/oauth2/implicit/registration-3");
+ assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=token&client_id=client-3&scope=openid\\+profile\\+email&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fimplicit%2Fregistration-3");
}
@Test
@@ -239,75 +254,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
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/login/oauth2/code/registration-1");
- }
-
- @Test
- public void doFilterWhenAuthorizationRequestRedirectUriTemplatedThenRedirectUriExpanded() 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);
-
- AuthorizationRequestRepository authorizationRequestRepository =
- mock(AuthorizationRequestRepository.class);
- this.filter.setAuthorizationRequestRepository(authorizationRequestRepository);
-
- this.filter.doFilter(request, response, filterChain);
-
- ArgumentCaptor authorizationRequestArgCaptor =
- ArgumentCaptor.forClass(OAuth2AuthorizationRequest.class);
-
- verifyZeroInteractions(filterChain);
- verify(authorizationRequestRepository).saveAuthorizationRequest(
- authorizationRequestArgCaptor.capture(), any(HttpServletRequest.class), any(HttpServletResponse.class));
-
- OAuth2AuthorizationRequest authorizationRequest = authorizationRequestArgCaptor.getValue();
-
- assertThat(authorizationRequest.getRedirectUri()).isNotEqualTo(
- this.registration2.getRedirectUriTemplate());
- assertThat(authorizationRequest.getRedirectUri()).isEqualTo(
- "http://localhost/login/oauth2/code/registration-2");
- }
-
- @Test
- public void doFilterWhenAuthorizationRequestIncludesPort80ThenExpandedRedirectUriExcludesPort() throws Exception {
- String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
- "/" + this.registration1.getRegistrationId();
- MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
- request.setScheme("http");
- request.setServerName("example.com");
- request.setServerPort(80);
- 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://example.com/login/oauth2/code/registration-1");
- }
-
- @Test
- public void doFilterWhenAuthorizationRequestIncludesPort443ThenExpandedRedirectUriExcludesPort() throws Exception {
- String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
- "/" + this.registration1.getRegistrationId();
- MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
- request.setScheme("https");
- request.setServerName("example.com");
- request.setServerPort(443);
- 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=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=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-1");
}
@Test
@@ -325,13 +272,13 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
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");
-
+ assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fregistration-1");
verify(this.requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
+ assertThat(request.getAttribute(AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME)).isNull();
}
@Test
- public void doFilterWhenNotAuthorizationRequestAndClientAuthorizationRequiredExceptionThrownThenRedirectUriIsAuthorize() throws Exception {
+ public void doFilterWhenNotAuthorizationRequestAndClientAuthorizationRequiredExceptionThrownButAuthorizationRequestNotResolvedThenStatusInternalServerError() throws Exception {
String requestUri = "/path";
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
@@ -341,60 +288,84 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
doThrow(new ClientAuthorizationRequiredException(this.registration1.getRegistrationId()))
.when(filterChain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
- this.filter.doFilter(request, response, filterChain);
+ OAuth2AuthorizationRequestResolver resolver = req -> null;
+ OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(resolver);
+
+ 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");
+ assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
+ assertThat(response.getErrorMessage()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
}
+ // gh-4911
@Test
- public void doFilterWhenAuthorizationRequestHasActionParameterAuthorizeThenRedirectUriIsAuthorize() throws Exception {
+ public void doFilterWhenAuthorizationRequestAndAdditionalParametersProvidedThenAuthorizationRequestIncludesAdditionalParameters() 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);
+ request.addParameter("idp", "https://other.provider.com");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
- this.filter.doFilter(request, response, filterChain);
+ OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(
+ this.clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
+
+ OAuth2AuthorizationRequestResolver resolver = req -> {
+ OAuth2AuthorizationRequest defaultAuthorizationRequest = defaultAuthorizationRequestResolver.resolve(req);
+ Map additionalParameters = new HashMap<>(defaultAuthorizationRequest.getAdditionalParameters());
+ additionalParameters.put("idp", req.getParameter("idp"));
+ return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest)
+ .additionalParameters(additionalParameters)
+ .build();
+ };
+ OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(resolver);
+
+ 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");
+ assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-1&idp=https%3A%2F%2Fother.provider.com");
}
+ // gh-4911, gh-5244
@Test
- public void doFilterWhenAuthorizationRequestHasActionParameterLoginThenRedirectUriIsLogin() throws Exception {
+ public void doFilterWhenAuthorizationRequestAndCustomAuthorizationRequestUriSetThenCustomAuthorizationRequestUriUsed() throws Exception {
String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +
- "/" + this.registration2.getRegistrationId();
+ "/" + this.registration1.getRegistrationId();
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
- request.addParameter("action", "login");
request.setServletPath(requestUri);
+ String loginHintParamName = "login_hint";
+ request.addParameter(loginHintParamName, "user@provider.com");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
- this.filter.doFilter(request, response, filterChain);
+ OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(
+ this.clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
+
+ OAuth2AuthorizationRequestResolver resolver = req -> {
+ OAuth2AuthorizationRequest defaultAuthorizationRequest = defaultAuthorizationRequestResolver.resolve(req);
+ Map additionalParameters = new HashMap<>(defaultAuthorizationRequest.getAdditionalParameters());
+ additionalParameters.put(loginHintParamName, req.getParameter(loginHintParamName));
+ String customAuthorizationRequestUri = UriComponentsBuilder
+ .fromUriString(defaultAuthorizationRequest.getAuthorizationRequestUri())
+ .queryParam(loginHintParamName, additionalParameters.get(loginHintParamName))
+ .build(true).toUriString();
+ return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest)
+ .additionalParameters(additionalParameters)
+ .authorizationRequestUri(customAuthorizationRequestUri)
+ .build();
+ };
+ OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(resolver);
+
+ 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");
+ assertThat(response.getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-1&login_hint=user@provider\\.com");
}
}
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestUriBuilderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestUriBuilderTests.java
deleted file mode 100644
index 0fde656c87..0000000000
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestUriBuilderTests.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2002-2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.oauth2.client.web;
-
-import org.junit.Test;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-
-import java.net.URI;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link OAuth2AuthorizationRequestUriBuilder}.
- *
- * @author Rob Winch
- * @since 5.0
- */
-public class OAuth2AuthorizationRequestUriBuilderTests {
- private OAuth2AuthorizationRequestUriBuilder builder = new OAuth2AuthorizationRequestUriBuilder();
-
- @Test(expected = IllegalArgumentException.class)
- public void buildWhenAuthorizationRequestIsNullThenThrowIllegalArgumentException() {
- this.builder.build(null);
- }
-
- @Test
- public void buildWhenScopeMultiThenSeparatedByEncodedSpace() {
- OAuth2AuthorizationRequest request = OAuth2AuthorizationRequest.implicit()
- .additionalParameters(Collections.singletonMap("foo", "bar"))
- .authorizationUri("https://idp.example.com/oauth2/v2/auth")
- .clientId("client-id")
- .state("thestate")
- .redirectUri("https://client.example.com/login/oauth2")
- .scopes(new HashSet<>(Arrays.asList("openid", "user")))
- .build();
-
- URI result = this.builder.build(request);
-
- assertThat(result.toASCIIString()).isEqualTo("https://idp.example.com/oauth2/v2/auth?response_type=token&client_id=client-id&scope=openid%20user&state=thestate&redirect_uri=https://client.example.com/login/oauth2");
- }
-}
diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
index 78eaac5598..3db5f3541d 100644
--- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
+++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
@@ -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");
* you may not use this file except in compliance with the License.
@@ -19,14 +19,18 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
+import java.util.StringJoiner;
import java.util.stream.Collectors;
/**
@@ -50,6 +54,7 @@ public final class OAuth2AuthorizationRequest implements Serializable {
private Set scopes;
private String state;
private Map additionalParameters;
+ private String authorizationRequestUri;
private OAuth2AuthorizationRequest() {
}
@@ -126,6 +131,20 @@ public final class OAuth2AuthorizationRequest implements Serializable {
return this.additionalParameters;
}
+ /**
+ * Returns the {@code URI} string representation of the OAuth 2.0 Authorization Request.
+ *
+ *
+ * NOTE: The {@code URI} string is encoded in the
+ * {@code application/x-www-form-urlencoded} MIME format.
+ *
+ * @since 5.1
+ * @return the {@code URI} string representation of the OAuth 2.0 Authorization Request
+ */
+ public String getAuthorizationRequestUri() {
+ return this.authorizationRequestUri;
+ }
+
/**
* Returns a new {@link Builder}, initialized with the authorization code grant type.
*
@@ -144,6 +163,26 @@ public final class OAuth2AuthorizationRequest implements Serializable {
return new Builder(AuthorizationGrantType.IMPLICIT);
}
+ /**
+ * Returns a new {@link Builder}, initialized with the values
+ * from the provided {@code authorizationRequest}.
+ *
+ * @since 5.1
+ * @param authorizationRequest the authorization request used for initializing the {@link Builder}
+ * @return the {@link Builder}
+ */
+ public static Builder from(OAuth2AuthorizationRequest authorizationRequest) {
+ Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
+
+ return new Builder(authorizationRequest.getGrantType())
+ .authorizationUri(authorizationRequest.getAuthorizationUri())
+ .clientId(authorizationRequest.getClientId())
+ .redirectUri(authorizationRequest.getRedirectUri())
+ .scopes(authorizationRequest.getScopes())
+ .state(authorizationRequest.getState())
+ .additionalParameters(authorizationRequest.getAdditionalParameters());
+ }
+
/**
* A builder for {@link OAuth2AuthorizationRequest}.
*/
@@ -156,6 +195,7 @@ public final class OAuth2AuthorizationRequest implements Serializable {
private Set scopes;
private String state;
private Map additionalParameters;
+ private String authorizationRequestUri;
private Builder(AuthorizationGrantType authorizationGrantType) {
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
@@ -247,6 +287,22 @@ public final class OAuth2AuthorizationRequest implements Serializable {
return this;
}
+ /**
+ * Sets the {@code URI} string representation of the OAuth 2.0 Authorization Request.
+ *
+ *
+ * NOTE: The {@code URI} string is required to be encoded in the
+ * {@code application/x-www-form-urlencoded} MIME format.
+ *
+ * @since 5.1
+ * @param authorizationRequestUri the {@code URI} string representation of the OAuth 2.0 Authorization Request
+ * @return the {@link Builder}
+ */
+ public Builder authorizationRequestUri(String authorizationRequestUri) {
+ this.authorizationRequestUri = authorizationRequestUri;
+ return this;
+ }
+
/**
* Builds a new {@link OAuth2AuthorizationRequest}.
*
@@ -272,7 +328,42 @@ public final class OAuth2AuthorizationRequest implements Serializable {
authorizationRequest.additionalParameters = Collections.unmodifiableMap(
CollectionUtils.isEmpty(this.additionalParameters) ?
Collections.emptyMap() : new LinkedHashMap<>(this.additionalParameters));
+ authorizationRequest.authorizationRequestUri =
+ StringUtils.hasText(this.authorizationRequestUri) ?
+ this.authorizationRequestUri : this.buildAuthorizationRequestUri();
+
return authorizationRequest;
}
+
+ private String buildAuthorizationRequestUri() {
+ Map parameters = new LinkedHashMap<>();
+ parameters.put(OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.getValue());
+ parameters.put(OAuth2ParameterNames.CLIENT_ID, this.clientId);
+ if (!CollectionUtils.isEmpty(this.scopes)) {
+ parameters.put(OAuth2ParameterNames.SCOPE,
+ StringUtils.collectionToDelimitedString(this.scopes, " "));
+ }
+ if (this.state != null) {
+ parameters.put(OAuth2ParameterNames.STATE, this.state);
+ }
+ if (this.redirectUri != null) {
+ parameters.put(OAuth2ParameterNames.REDIRECT_URI, this.redirectUri);
+ }
+ if (!CollectionUtils.isEmpty(this.additionalParameters)) {
+ this.additionalParameters.entrySet().stream()
+ .filter(e -> !e.getKey().equals(OAuth2ParameterNames.REGISTRATION_ID))
+ .forEach(e -> parameters.put(e.getKey(), e.getValue().toString()));
+ }
+
+ try {
+ StringJoiner queryParams = new StringJoiner("&");
+ for (String paramName : parameters.keySet()) {
+ queryParams.add(paramName + "=" + URLEncoder.encode(parameters.get(paramName), "UTF-8"));
+ }
+ return this.authorizationUri + "?" + queryParams.toString();
+ } catch (UnsupportedEncodingException ex) {
+ throw new IllegalArgumentException("Unable to build authorization request uri: " + ex.getMessage(), ex);
+ }
+ }
}
}
diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java
index 70ccd4caf1..d61516785a 100644
--- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java
+++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java
@@ -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");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
package org.springframework.security.oauth2.core.endpoint;
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.core.AuthorizationGrantType;
import java.util.Arrays;
@@ -27,8 +24,7 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.*;
/**
* Tests for {@link OAuth2AuthorizationRequest}.
@@ -36,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatCode;
* @author Luander Ribeiro
* @author Joe Grandja
*/
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(OAuth2AuthorizationRequest.class)
public class OAuth2AuthorizationRequestTests {
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
private static final String CLIENT_ID = "client-id";
@@ -45,59 +39,107 @@ public class OAuth2AuthorizationRequestTests {
private static final Set SCOPES = new LinkedHashSet<>(Arrays.asList("scope1", "scope2"));
private static final String STATE = "state";
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void buildWhenAuthorizationUriIsNullThenThrowIllegalArgumentException() {
- OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(null)
- .clientId(CLIENT_ID)
- .redirectUri(REDIRECT_URI)
- .scopes(SCOPES)
- .state(STATE)
- .build();
+ assertThatThrownBy(() ->
+ OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(null)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build()
+ ).isInstanceOf(IllegalArgumentException.class);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void buildWhenClientIdIsNullThenThrowIllegalArgumentException() {
- OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(null)
- .redirectUri(REDIRECT_URI)
- .scopes(SCOPES)
- .state(STATE)
- .build();
+ assertThatThrownBy(() ->
+ OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(null)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build()
+ ).isInstanceOf(IllegalArgumentException.class);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void buildWhenRedirectUriIsNullForImplicitThenThrowIllegalArgumentException() {
- OAuth2AuthorizationRequest.implicit()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(null)
- .scopes(SCOPES)
- .state(STATE)
- .build();
+ assertThatThrownBy(() ->
+ OAuth2AuthorizationRequest.implicit()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(null)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build()
+ ).isInstanceOf(IllegalArgumentException.class);
}
@Test
public void buildWhenRedirectUriIsNullForAuthorizationCodeThenDoesNotThrowAnyException() {
- assertThatCode(() -> OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(null)
- .scopes(SCOPES)
- .state(STATE)
- .build()).doesNotThrowAnyException();
+ assertThatCode(() ->
+ OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(null)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build())
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void buildWhenScopesIsNullThenDoesNotThrowAnyException() {
+ assertThatCode(() ->
+ OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(null)
+ .state(STATE)
+ .build())
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void buildWhenStateIsNullThenDoesNotThrowAnyException() {
+ assertThatCode(() ->
+ OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(null)
+ .build())
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void buildWhenAdditionalParametersIsNullThenDoesNotThrowAnyException() {
+ assertThatCode(() ->
+ OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .additionalParameters(null)
+ .build())
+ .doesNotThrowAnyException();
}
@Test
public void buildWhenImplicitThenGrantTypeResponseTypeIsSet() {
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.implicit()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(REDIRECT_URI)
- .scopes(SCOPES)
- .state(STATE)
- .build();
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build();
assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.IMPLICIT);
assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.TOKEN);
}
@@ -105,30 +147,31 @@ public class OAuth2AuthorizationRequestTests {
@Test
public void buildWhenAuthorizationCodeThenGrantTypeResponseTypeIsSet() {
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(null)
- .scopes(SCOPES)
- .state(STATE)
- .build();
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(null)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build();
assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.CODE);
}
@Test
- public void buildWhenAllAttributesProvidedThenAllAttributesAreSet() {
+ public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
Map additionalParameters = new HashMap<>();
additionalParameters.put("param1", "value1");
additionalParameters.put("param2", "value2");
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(REDIRECT_URI)
- .scopes(SCOPES)
- .state(STATE)
- .additionalParameters(additionalParameters)
- .build();
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .additionalParameters(additionalParameters)
+ .authorizationRequestUri(AUTHORIZATION_URI)
+ .build();
assertThat(authorizationRequest.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
@@ -138,39 +181,113 @@ public class OAuth2AuthorizationRequestTests {
assertThat(authorizationRequest.getScopes()).isEqualTo(SCOPES);
assertThat(authorizationRequest.getState()).isEqualTo(STATE);
assertThat(authorizationRequest.getAdditionalParameters()).isEqualTo(additionalParameters);
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo(AUTHORIZATION_URI);
}
@Test
- public void buildWhenScopesIsNullThenDoesNotThrowAnyException() {
- assertThatCode(() -> OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(REDIRECT_URI)
- .scopes(null)
- .state(STATE)
- .build()).doesNotThrowAnyException();
+ public void buildWhenScopesMultiThenSeparatedByEncodedSpace() {
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.implicit()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .build();
+
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=token&client_id=client-id&scope=scope1+scope2&state=state&redirect_uri=http%3A%2F%2Fexample.com");
}
@Test
- public void buildWhenStateIsNullThenDoesNotThrowAnyException() {
- assertThatCode(() -> OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(REDIRECT_URI)
- .scopes(SCOPES)
- .state(null)
- .build()).doesNotThrowAnyException();
+ public void buildWhenAuthorizationRequestUriSetThenOverridesDefault() {
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .authorizationRequestUri(AUTHORIZATION_URI)
+ .build();
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo(AUTHORIZATION_URI);
}
@Test
- public void buildWhenAdditionalParametersIsNullThenDoesNotThrowAnyException() {
- assertThatCode(() -> OAuth2AuthorizationRequest.authorizationCode()
- .authorizationUri(AUTHORIZATION_URI)
- .clientId(CLIENT_ID)
- .redirectUri(REDIRECT_URI)
- .scopes(SCOPES)
- .state(STATE)
- .additionalParameters(null)
- .build()).doesNotThrowAnyException();
+ public void buildWhenAuthorizationRequestUriNotSetThenDefaultSet() {
+ Map additionalParameters = new HashMap<>();
+ additionalParameters.put("param1", "value1");
+ additionalParameters.put("param2", "value2");
+
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .additionalParameters(additionalParameters)
+ .build();
+
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull();
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=code&client_id=client-id&scope=scope1+scope2&state=state&redirect_uri=http%3A%2F%2Fexample.com¶m1=value1¶m2=value2");
+ }
+
+ @Test
+ public void buildWhenRequiredParametersSetThenAuthorizationRequestUriIncludesRequiredParametersOnly() {
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .build();
+
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=code&client_id=client-id");
+ }
+
+ @Test
+ public void buildWhenAuthorizationRequestIncludesRegistrationIdParameterThenAuthorizationRequestUriDoesNotIncludeRegistrationIdParameter() {
+ Map additionalParameters = new HashMap<>();
+ additionalParameters.put("param1", "value1");
+ additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, "registration1");
+
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .additionalParameters(additionalParameters)
+ .build();
+
+ assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=code&client_id=client-id&scope=scope1+scope2&state=state&redirect_uri=http%3A%2F%2Fexample.com¶m1=value1");
+ }
+
+ @Test
+ public void fromWhenAuthorizationRequestIsNullThenThrowIllegalArgumentException() {
+ assertThatThrownBy(() -> OAuth2AuthorizationRequest.from(null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void fromWhenAuthorizationRequestProvidedThenValuesAreCopied() {
+ Map additionalParameters = new HashMap<>();
+ additionalParameters.put("param1", "value1");
+ additionalParameters.put("param2", "value2");
+
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(AUTHORIZATION_URI)
+ .clientId(CLIENT_ID)
+ .redirectUri(REDIRECT_URI)
+ .scopes(SCOPES)
+ .state(STATE)
+ .additionalParameters(additionalParameters)
+ .build();
+
+ OAuth2AuthorizationRequest authorizationRequestCopy =
+ OAuth2AuthorizationRequest.from(authorizationRequest).build();
+
+ assertThat(authorizationRequestCopy.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri());
+ assertThat(authorizationRequestCopy.getGrantType()).isEqualTo(authorizationRequest.getGrantType());
+ assertThat(authorizationRequestCopy.getResponseType()).isEqualTo(authorizationRequest.getResponseType());
+ assertThat(authorizationRequestCopy.getClientId()).isEqualTo(authorizationRequest.getClientId());
+ assertThat(authorizationRequestCopy.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri());
+ assertThat(authorizationRequestCopy.getScopes()).isEqualTo(authorizationRequest.getScopes());
+ assertThat(authorizationRequestCopy.getState()).isEqualTo(authorizationRequest.getState());
+ assertThat(authorizationRequestCopy.getAdditionalParameters()).isEqualTo(authorizationRequest.getAdditionalParameters());
+ assertThat(authorizationRequestCopy.getAuthorizationRequestUri()).isEqualTo(authorizationRequest.getAuthorizationRequestUri());
}
}
diff --git a/samples/boot/authcodegrant/src/integration-test/java/org/springframework/security/samples/OAuth2AuthorizationCodeGrantApplicationTests.java b/samples/boot/authcodegrant/src/integration-test/java/org/springframework/security/samples/OAuth2AuthorizationCodeGrantApplicationTests.java
index 36ee34e365..b078b6aacb 100644
--- a/samples/boot/authcodegrant/src/integration-test/java/org/springframework/security/samples/OAuth2AuthorizationCodeGrantApplicationTests.java
+++ b/samples/boot/authcodegrant/src/integration-test/java/org/springframework/security/samples/OAuth2AuthorizationCodeGrantApplicationTests.java
@@ -88,7 +88,7 @@ public class OAuth2AuthorizationCodeGrantApplicationTests {
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");
+ 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%3A%2F%2Flocalhost%2Fgithub-repos");
}
@Test