mirror of https://github.com/apache/nifi.git
NIFI-11365 Corrected OIDC Redirect URI resolution for reverse proxies (#7104)
- Added Authorization Request Resolver with support for building the base redirect URI using allowed proxy headers This closes #7104
This commit is contained in:
parent
08e18220e7
commit
75eb449a31
|
@ -38,6 +38,7 @@ import org.apache.nifi.web.security.oidc.OidcConfigurationException;
|
||||||
import org.apache.nifi.web.security.oidc.OidcUrlPath;
|
import org.apache.nifi.web.security.oidc.OidcUrlPath;
|
||||||
import org.apache.nifi.web.security.oidc.client.web.AuthorizedClientExpirationCommand;
|
import org.apache.nifi.web.security.oidc.client.web.AuthorizedClientExpirationCommand;
|
||||||
import org.apache.nifi.web.security.oidc.client.web.OidcBearerTokenRefreshFilter;
|
import org.apache.nifi.web.security.oidc.client.web.OidcBearerTokenRefreshFilter;
|
||||||
|
import org.apache.nifi.web.security.oidc.client.web.StandardOAuth2AuthorizationRequestResolver;
|
||||||
import org.apache.nifi.web.security.oidc.client.web.converter.AuthenticationResultConverter;
|
import org.apache.nifi.web.security.oidc.client.web.converter.AuthenticationResultConverter;
|
||||||
import org.apache.nifi.web.security.oidc.client.web.converter.AuthorizedClientConverter;
|
import org.apache.nifi.web.security.oidc.client.web.converter.AuthorizedClientConverter;
|
||||||
import org.apache.nifi.web.security.oidc.client.web.StandardAuthorizationRequestRepository;
|
import org.apache.nifi.web.security.oidc.client.web.StandardAuthorizationRequestRepository;
|
||||||
|
@ -187,7 +188,8 @@ public class OidcSecurityConfiguration {
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public OAuth2AuthorizationRequestRedirectFilter oAuth2AuthorizationRequestRedirectFilter() {
|
public OAuth2AuthorizationRequestRedirectFilter oAuth2AuthorizationRequestRedirectFilter() {
|
||||||
final OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(clientRegistrationRepository());
|
final StandardOAuth2AuthorizationRequestResolver authorizationRequestResolver = new StandardOAuth2AuthorizationRequestResolver(clientRegistrationRepository());
|
||||||
|
final OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(authorizationRequestResolver);
|
||||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository());
|
filter.setAuthorizationRequestRepository(authorizationRequestRepository());
|
||||||
filter.setRequestCache(nullRequestCache);
|
filter.setRequestCache(nullRequestCache);
|
||||||
return filter;
|
return filter;
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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.apache.nifi.web.security.oidc.client.web;
|
||||||
|
|
||||||
|
import org.apache.nifi.web.util.RequestUriBuilder;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
|
||||||
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||||
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorization Request Resolver supports handling of headers from reverse proxy servers
|
||||||
|
*/
|
||||||
|
public class StandardOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
|
||||||
|
private final OAuth2AuthorizationRequestResolver resolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolver constructor delegates to the Spring Security Default Resolver and uses the default Request Base URI
|
||||||
|
*
|
||||||
|
* @param clientRegistrationRepository Client Registration Repository
|
||||||
|
*/
|
||||||
|
public StandardOAuth2AuthorizationRequestResolver(final ClientRegistrationRepository clientRegistrationRepository) {
|
||||||
|
Objects.requireNonNull(clientRegistrationRepository, "Repository required");
|
||||||
|
resolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve Authorization Request delegating to default resolver
|
||||||
|
*
|
||||||
|
* @param request HTTP Servlet Request
|
||||||
|
* @return OAuth2 Authorization Request or null when not resolved
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OAuth2AuthorizationRequest resolve(final HttpServletRequest request) {
|
||||||
|
final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(request);
|
||||||
|
return getResolvedAuthorizationRequest(authorizationRequest, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve Authorization Request delegating to default resolver
|
||||||
|
*
|
||||||
|
* @param request HTTP Servlet Request
|
||||||
|
* @param clientRegistrationId Client Registration Identifier
|
||||||
|
* @return OAuth2 Authorization Request or null when not resolved
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OAuth2AuthorizationRequest resolve(final HttpServletRequest request, final String clientRegistrationId) {
|
||||||
|
final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(request, clientRegistrationId);
|
||||||
|
return getResolvedAuthorizationRequest(authorizationRequest, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2AuthorizationRequest getResolvedAuthorizationRequest(final OAuth2AuthorizationRequest authorizationRequest, final HttpServletRequest request) {
|
||||||
|
final OAuth2AuthorizationRequest resolvedAuthorizationRequest;
|
||||||
|
|
||||||
|
if (authorizationRequest == null) {
|
||||||
|
resolvedAuthorizationRequest = null;
|
||||||
|
} else {
|
||||||
|
final String redirectUri = authorizationRequest.getRedirectUri();
|
||||||
|
if (redirectUri == null) {
|
||||||
|
resolvedAuthorizationRequest = authorizationRequest;
|
||||||
|
} else {
|
||||||
|
final String requestBasedRedirectUri = getRequestBasedRedirectUri(redirectUri, request);
|
||||||
|
resolvedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest).redirectUri(requestBasedRedirectUri).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedAuthorizationRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequestBasedRedirectUri(final String redirectUri, final HttpServletRequest request) {
|
||||||
|
final String redirectUriPath = UriComponentsBuilder.fromUriString(redirectUri).build().getPath();
|
||||||
|
final URI baseUri = RequestUriBuilder.fromHttpServletRequest(request).path(redirectUriPath).build();
|
||||||
|
return UriComponentsBuilder.fromUriString(redirectUri)
|
||||||
|
.scheme(baseUri.getScheme())
|
||||||
|
.host(baseUri.getHost())
|
||||||
|
.port(baseUri.getPort())
|
||||||
|
.replacePath(baseUri.getPath())
|
||||||
|
.build()
|
||||||
|
.toUriString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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.apache.nifi.web.security.oidc.client.web;
|
||||||
|
|
||||||
|
import org.apache.nifi.web.util.WebUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StandardOAuth2AuthorizationRequestResolverTest {
|
||||||
|
private static final String REDIRECT_URI = "https://localhost:8443/nifi-api/callback";
|
||||||
|
|
||||||
|
private static final String FORWARDED_PATH = "/forwarded";
|
||||||
|
|
||||||
|
private static final String FORWARDED_REDIRECT_URI = String.format("https://localhost.localdomain%s/nifi-api/callback", FORWARDED_PATH);
|
||||||
|
|
||||||
|
private static final String ALLOWED_CONTEXT_PATHS_PARAMETER = "allowedContextPaths";
|
||||||
|
|
||||||
|
private static final String AUTHORIZATION_URI = "http://localhost/authorize";
|
||||||
|
|
||||||
|
private static final String TOKEN_URI = "http://localhost/token";
|
||||||
|
|
||||||
|
private static final String CLIENT_ID = "client-id";
|
||||||
|
|
||||||
|
private static final String REGISTRATION_ID = OidcRegistrationProperty.REGISTRATION_ID.getProperty();
|
||||||
|
|
||||||
|
MockHttpServletRequest httpServletRequest;
|
||||||
|
|
||||||
|
MockHttpServletResponse httpServletResponse;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
|
StandardOAuth2AuthorizationRequestResolver resolver;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setResolver() {
|
||||||
|
resolver = new StandardOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
|
||||||
|
httpServletRequest = new MockHttpServletRequest();
|
||||||
|
httpServletResponse = new MockHttpServletResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolveNotFound() {
|
||||||
|
final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest);
|
||||||
|
|
||||||
|
assertNull(authorizationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolveClientRegistrationIdNotFound() {
|
||||||
|
final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest, null);
|
||||||
|
|
||||||
|
assertNull(authorizationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolveFound() {
|
||||||
|
final URI redirectUri = URI.create(REDIRECT_URI);
|
||||||
|
httpServletRequest.setScheme(redirectUri.getScheme());
|
||||||
|
httpServletRequest.setServerPort(redirectUri.getPort());
|
||||||
|
|
||||||
|
final ClientRegistration clientRegistration = getClientRegistration();
|
||||||
|
when(clientRegistrationRepository.findByRegistrationId(eq(REGISTRATION_ID))).thenReturn(clientRegistration);
|
||||||
|
|
||||||
|
final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest, REGISTRATION_ID);
|
||||||
|
|
||||||
|
assertNotNull(authorizationRequest);
|
||||||
|
assertEquals(REDIRECT_URI, authorizationRequest.getRedirectUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolveFoundRedirectUriProxyHeaders() {
|
||||||
|
final ClientRegistration clientRegistration = getClientRegistration();
|
||||||
|
when(clientRegistrationRepository.findByRegistrationId(eq(REGISTRATION_ID))).thenReturn(clientRegistration);
|
||||||
|
|
||||||
|
final ServletContext servletContext = httpServletRequest.getServletContext();
|
||||||
|
servletContext.setInitParameter(ALLOWED_CONTEXT_PATHS_PARAMETER, FORWARDED_PATH);
|
||||||
|
|
||||||
|
final URI forwardedRedirectUri = URI.create(FORWARDED_REDIRECT_URI);
|
||||||
|
httpServletRequest.addHeader(WebUtils.PROXY_SCHEME_HTTP_HEADER, forwardedRedirectUri.getScheme());
|
||||||
|
httpServletRequest.addHeader(WebUtils.PROXY_HOST_HTTP_HEADER, forwardedRedirectUri.getHost());
|
||||||
|
httpServletRequest.addHeader(WebUtils.PROXY_PORT_HTTP_HEADER, forwardedRedirectUri.getPort());
|
||||||
|
httpServletRequest.addHeader(WebUtils.PROXY_CONTEXT_PATH_HTTP_HEADER, FORWARDED_PATH);
|
||||||
|
|
||||||
|
final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest, REGISTRATION_ID);
|
||||||
|
|
||||||
|
assertNotNull(authorizationRequest);
|
||||||
|
assertEquals(FORWARDED_REDIRECT_URI, authorizationRequest.getRedirectUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientRegistration getClientRegistration() {
|
||||||
|
return ClientRegistration.withRegistrationId(OidcRegistrationProperty.REGISTRATION_ID.getProperty())
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.clientId(CLIENT_ID)
|
||||||
|
.redirectUri(REDIRECT_URI)
|
||||||
|
.authorizationUri(AUTHORIZATION_URI)
|
||||||
|
.tokenUri(TOKEN_URI)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue