Provide extension for processing authorization request
Issue gh-342
This commit is contained in:
@@ -17,11 +17,8 @@ package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
@@ -31,24 +28,20 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
@@ -60,7 +53,6 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -88,8 +80,8 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final RequestMatcher authorizationEndpointMatcher;
|
||||
private final AuthenticationConverter authenticationConverter;
|
||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
private String consentPage;
|
||||
|
||||
/**
|
||||
@@ -164,17 +156,6 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
return new OrRequestMatcher(authorizationRequestMatcher, authorizationConsentMatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the URI to redirect Resource Owners to if consent is required. A default consent
|
||||
* page will be generated when this attribute is not specified.
|
||||
*
|
||||
* @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
|
||||
* @see org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer#consentPage(String)
|
||||
*/
|
||||
public final void setConsentPage(String consentPage) {
|
||||
this.consentPage = consentPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
@@ -212,6 +193,28 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
*
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
*/
|
||||
public final void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the URI to redirect Resource Owners to if consent is required. A default consent
|
||||
* page will be generated when this attribute is not specified.
|
||||
*
|
||||
* @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
|
||||
* @see org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer#consentPage(String)
|
||||
*/
|
||||
public final void setConsentPage(String consentPage) {
|
||||
this.consentPage = consentPage;
|
||||
}
|
||||
|
||||
private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult) throws IOException {
|
||||
@@ -294,136 +297,6 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
|
||||
}
|
||||
|
||||
private static class OAuth2AuthorizationCodeRequestAuthenticationConverter implements AuthenticationConverter {
|
||||
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
|
||||
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1";
|
||||
private final RequestMatcher oidcAuthenticationRequestMatcher;
|
||||
|
||||
private OAuth2AuthorizationCodeRequestAuthenticationConverter() {
|
||||
RequestMatcher postMethodMatcher = request -> "POST".equals(request.getMethod());
|
||||
RequestMatcher responseTypeParameterMatcher = request ->
|
||||
request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null;
|
||||
RequestMatcher openidScopeMatcher = request -> {
|
||||
String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
|
||||
return StringUtils.hasText(scope) && scope.contains(OidcScopes.OPENID);
|
||||
};
|
||||
this.oidcAuthenticationRequestMatcher = new AndRequestMatcher(
|
||||
postMethodMatcher, responseTypeParameterMatcher, openidScopeMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||
|
||||
boolean authorizationRequest = false;
|
||||
if ("GET".equals(request.getMethod()) || this.oidcAuthenticationRequestMatcher.matches(request)) {
|
||||
authorizationRequest = true;
|
||||
|
||||
// response_type (REQUIRED)
|
||||
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
if (!StringUtils.hasText(responseType) ||
|
||||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
|
||||
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
String authorizationUri = request.getRequestURL().toString();
|
||||
|
||||
// client_id (REQUIRED)
|
||||
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
||||
if (!StringUtils.hasText(clientId) ||
|
||||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
|
||||
}
|
||||
|
||||
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (principal == null) {
|
||||
principal = ANONYMOUS_AUTHENTICATION;
|
||||
}
|
||||
|
||||
// redirect_uri (OPTIONAL)
|
||||
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
|
||||
if (StringUtils.hasText(redirectUri) &&
|
||||
parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
|
||||
}
|
||||
|
||||
// scope (OPTIONAL)
|
||||
Set<String> scopes = null;
|
||||
if (authorizationRequest) {
|
||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||
if (StringUtils.hasText(scope) &&
|
||||
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
|
||||
}
|
||||
if (StringUtils.hasText(scope)) {
|
||||
scopes = new HashSet<>(
|
||||
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||
}
|
||||
} else {
|
||||
// Consent request
|
||||
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
|
||||
scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
|
||||
}
|
||||
}
|
||||
|
||||
// state (RECOMMENDED)
|
||||
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
|
||||
if (StringUtils.hasText(state) &&
|
||||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
|
||||
}
|
||||
|
||||
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
|
||||
String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
|
||||
if (StringUtils.hasText(codeChallenge) &&
|
||||
parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI);
|
||||
}
|
||||
|
||||
// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
|
||||
String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||
if (StringUtils.hasText(codeChallengeMethod) &&
|
||||
parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
Map<String, Object> additionalParameters = parameters
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.SCOPE) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.STATE))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
||||
// @formatter:on
|
||||
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, principal)
|
||||
.authorizationUri(authorizationUri)
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(scopes)
|
||||
.state(state)
|
||||
.additionalParameters(additionalParameters)
|
||||
.consent(!authorizationRequest)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName) {
|
||||
throwError(errorCode, parameterName, "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName, String errorUri) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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
|
||||
*
|
||||
* https://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.server.authorization.web.authentication;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Attempts to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* for the OAuth 2.0 Authorization Code Grant and then converts it to
|
||||
* an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.2
|
||||
* @see AuthenticationConverter
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
|
||||
* @see OAuth2AuthorizationEndpointFilter
|
||||
*/
|
||||
public final class OAuth2AuthorizationCodeRequestAuthenticationConverter implements AuthenticationConverter {
|
||||
private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
|
||||
private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1";
|
||||
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
|
||||
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
private static final RequestMatcher OIDC_REQUEST_MATCHER = createOidcRequestMatcher();
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||
|
||||
boolean authorizationRequest = false;
|
||||
if ("GET".equals(request.getMethod()) || OIDC_REQUEST_MATCHER.matches(request)) {
|
||||
authorizationRequest = true;
|
||||
|
||||
// response_type (REQUIRED)
|
||||
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
if (!StringUtils.hasText(responseType) ||
|
||||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
|
||||
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
String authorizationUri = request.getRequestURL().toString();
|
||||
|
||||
// client_id (REQUIRED)
|
||||
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
||||
if (!StringUtils.hasText(clientId) ||
|
||||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
|
||||
}
|
||||
|
||||
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (principal == null) {
|
||||
principal = ANONYMOUS_AUTHENTICATION;
|
||||
}
|
||||
|
||||
// redirect_uri (OPTIONAL)
|
||||
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
|
||||
if (StringUtils.hasText(redirectUri) &&
|
||||
parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
|
||||
}
|
||||
|
||||
// scope (OPTIONAL)
|
||||
Set<String> scopes = null;
|
||||
if (authorizationRequest) {
|
||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||
if (StringUtils.hasText(scope) &&
|
||||
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
|
||||
}
|
||||
if (StringUtils.hasText(scope)) {
|
||||
scopes = new HashSet<>(
|
||||
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||
}
|
||||
} else {
|
||||
// Consent request
|
||||
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
|
||||
scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
|
||||
}
|
||||
}
|
||||
|
||||
// state (RECOMMENDED)
|
||||
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
|
||||
if (StringUtils.hasText(state) &&
|
||||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
|
||||
}
|
||||
|
||||
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
|
||||
String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
|
||||
if (StringUtils.hasText(codeChallenge) &&
|
||||
parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI);
|
||||
}
|
||||
|
||||
// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
|
||||
String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||
if (StringUtils.hasText(codeChallengeMethod) &&
|
||||
parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
Map<String, Object> additionalParameters = parameters
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.SCOPE) &&
|
||||
!e.getKey().equals(OAuth2ParameterNames.STATE))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
||||
// @formatter:on
|
||||
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, principal)
|
||||
.authorizationUri(authorizationUri)
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(scopes)
|
||||
.state(state)
|
||||
.additionalParameters(additionalParameters)
|
||||
.consent(!authorizationRequest)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static RequestMatcher createOidcRequestMatcher() {
|
||||
RequestMatcher postMethodMatcher = request -> "POST".equals(request.getMethod());
|
||||
RequestMatcher responseTypeParameterMatcher = request ->
|
||||
request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null;
|
||||
RequestMatcher openidScopeMatcher = request -> {
|
||||
String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
|
||||
return StringUtils.hasText(scope) && scope.contains(OidcScopes.OPENID);
|
||||
};
|
||||
return new AndRequestMatcher(
|
||||
postMethodMatcher, responseTypeParameterMatcher, openidScopeMatcher);
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName) {
|
||||
throwError(errorCode, parameterName, DEFAULT_ERROR_URI);
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName, String errorUri) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -110,6 +111,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.hasMessage("authorizationEndpointUri cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotAuthorizationRequestThenNotProcessed() throws Exception {
|
||||
String requestUri = "/path";
|
||||
@@ -242,6 +250,31 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?error=errorCode&error_description=errorDescription&error_uri=errorUri&state=state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
|
||||
AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
|
||||
when(authenticationConverter.convert(any())).thenReturn(authorizationCodeRequestAuthentication);
|
||||
this.filter.setAuthenticationConverter(authenticationConverter);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthentication);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(authenticationConverter).convert(any());
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
|
||||
this.principal.setAuthenticated(false);
|
||||
|
||||
Reference in New Issue
Block a user