Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper;
Expand All @@ -36,6 +38,7 @@
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Configuration
Expand Down Expand Up @@ -84,8 +87,14 @@ public SecurityWebFilterChain configure(ServerHttpSecurity http, OAuthLogoutSucc
}

@Bean
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> customOidcUserService(AccessControlService acs) {
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> customOidcUserService(
AccessControlService acs,
ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService) {
final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();

// Use our custom OAuth2 user service which may have proxy support
delegate.setOauth2UserService(oauth2UserService);

return request -> delegate.loadUser(request)
.flatMap(user -> {
var provider = getProviderByProviderId(request.getClientRegistration().getRegistrationId());
Expand All @@ -100,8 +109,17 @@ public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> customOidcUserServic
}

@Bean
public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> customOauth2UserService(AccessControlService acs) {
public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> customOauth2UserService(
AccessControlService acs,
@Autowired(required = false) @Qualifier("oauth2WebClient") WebClient oauth2WebClient) {
final DefaultReactiveOAuth2UserService delegate = new DefaultReactiveOAuth2UserService();

// If proxy-configured WebClient is available, use it
if (oauth2WebClient != null) {
delegate.setWebClient(oauth2WebClient);
log.debug("OAuth2 user service configured with custom WebClient (proxy support)");
}

return request -> delegate.loadUser(request)
.flatMap(user -> {
var provider = getProviderByProviderId(request.getClientRegistration().getRegistrationId());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.kafbat.ui.config.auth;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

@Slf4j
@Configuration
@ConditionalOnProperty(value = "auth.type", havingValue = "OAUTH2")
@EnableConfigurationProperties(SimpleOAuthProxyConfig.ProxyProperties.class)
public class SimpleOAuthProxyConfig {

@Data
@ConfigurationProperties("auth.oauth2.proxy")
public static class ProxyProperties {
private boolean enabled = false;
private String host;
private Integer port;
}

@Bean(name = "oauth2WebClient")
@ConditionalOnProperty(value = "auth.oauth2.proxy.enabled", havingValue = "true")
public WebClient oauth2WebClient(ProxyProperties proxyProperties) {
HttpClient httpClient;

if (proxyProperties.getHost() != null && proxyProperties.getPort() != null) {
// Use explicit proxy configuration
log.info("OAuth2 configured with explicit proxy: {}:{}",
proxyProperties.getHost(), proxyProperties.getPort());

httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(reactor.netty.transport.ProxyProvider.Proxy.HTTP)
.host(proxyProperties.getHost())
.port(proxyProperties.getPort()));
} else {
// Use system proxy properties
log.info("OAuth2 configured to use system proxy properties");
httpClient = HttpClient.create().proxyWithSystemProperties();
}

return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package io.kafbat.ui.config.auth;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;

class SimpleOAuthProxyConfigIntegrationTest {

private final MockWebServer oauth2Server = new MockWebServer();

@BeforeEach
void startMockServer() throws IOException {
oauth2Server.start();
}

@AfterEach
void stopMockServer() throws IOException {
oauth2Server.close();
}

@Test
void testWebClientWithExplicitProxyCanMakeRequests() throws Exception {
// Create proxy config - in real usage, this would point to a proxy server
var proxyProps = new SimpleOAuthProxyConfig.ProxyProperties();
proxyProps.setEnabled(true);
proxyProps.setHost("proxy.example.com");
proxyProps.setPort(8080);

// Create WebClient with proxy configuration
var config = new SimpleOAuthProxyConfig();
WebClient webClient = config.oauth2WebClient(proxyProps);
assertThat(webClient).isNotNull();

// Mock OAuth2 server response
oauth2Server.enqueue(new MockResponse()
.setResponseCode(200)
.setBody("{\"access_token\": \"test-token\", \"token_type\": \"Bearer\"}")
.addHeader("Content-Type", "application/json"));

// Make a request to the mock server directly (not through proxy, since MockWebServer can't act as proxy)
// This tests that WebClient works and doesn't throw exceptions with proxy config
String response = webClient
.get()
.uri(oauth2Server.url("/oauth/token").toString())
.retrieve()
.bodyToMono(String.class)
.onErrorReturn("{\"error\": \"proxy_not_available\"}") // Expected since proxy doesn't exist
.block();

// In a real environment with a proxy, this would succeed
// Here we just verify the WebClient was created with proxy config
assertThat(response).contains("proxy_not_available");
}

@Test
void testWebClientWithSystemProxyProperties() {
// Set system properties
System.setProperty("https.proxyHost", "proxy.example.com");
System.setProperty("https.proxyPort", "8080");

try {
var proxyProps = new SimpleOAuthProxyConfig.ProxyProperties();
proxyProps.setEnabled(true);
// Don't set host/port - should use system properties

var config = new SimpleOAuthProxyConfig();
WebClient webClient = config.oauth2WebClient(proxyProps);

// Verify WebClient was created successfully with system proxy
assertThat(webClient).isNotNull();
// Note: Can't easily test actual system proxy routing without a real proxy server
// But this verifies the configuration doesn't throw exceptions
} finally {
// Clean up system properties
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
}
}

@Test
void testProxyDisabledDoesNotCreateWebClient() {
var proxyProps = new SimpleOAuthProxyConfig.ProxyProperties();
proxyProps.setEnabled(false);

// When proxy is disabled, the bean should not be created
// This would normally be handled by Spring's @ConditionalOnProperty
// In unit test, we just verify the properties default
assertThat(proxyProps.isEnabled()).isFalse();
}

@Test
void testWebClientCanMakeRequestsToOAuth2Provider() throws Exception {
// This test verifies the WebClient can communicate with an OAuth2 provider
// In production, the proxy would be between the WebClient and the OAuth2 provider

// Create a simple WebClient without proxy (simulating direct connection)
var config = new SimpleOAuthProxyConfig();
var proxyProps = new SimpleOAuthProxyConfig.ProxyProperties();
proxyProps.setEnabled(true);
proxyProps.setHost("localhost");
proxyProps.setPort(oauth2Server.getPort());

// Note: We can't actually test proxy behavior with MockWebServer
// as it doesn't implement proper proxy protocol (CONNECT method)
WebClient webClient = config.oauth2WebClient(proxyProps);

// Mock OAuth2 token endpoint
oauth2Server.enqueue(new MockResponse()
.setResponseCode(200)
.setBody("{\"access_token\": \"test-token\", \"token_type\": \"Bearer\", \"expires_in\": 3600}")
.addHeader("Content-Type", "application/json"));

// Mock OAuth2 userinfo endpoint
oauth2Server.enqueue(new MockResponse()
.setResponseCode(200)
.setBody("{\"sub\": \"123456\", \"email\": \"[email protected]\"}")
.addHeader("Content-Type", "application/json"));

// Test token request (typical OAuth2 flow)
String tokenResponse = webClient.post()
.uri(oauth2Server.url("/oauth/token").toString())
.bodyValue("grant_type=authorization_code&code=test-code")
.retrieve()
.bodyToMono(String.class)
.onErrorReturn("{\"error\": \"connection_failed\"}")
.block();

// Test userinfo request (typical OAuth2 flow)
String userResponse = webClient.get()
.uri(oauth2Server.url("/oauth/userinfo").toString())
.header("Authorization", "Bearer test-token")
.retrieve()
.bodyToMono(String.class)
.onErrorReturn("{\"error\": \"connection_failed\"}")
.block();

// Verify WebClient was created and can attempt requests
// Actual proxy routing can only be verified with integration tests using real proxy
assertThat(webClient).isNotNull();
// The requests will fail because localhost:port is not a real proxy server
assertThat(tokenResponse).contains("connection_failed");
assertThat(userResponse).contains("connection_failed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.kafbat.ui.config.auth;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;

class SimpleOAuthProxyConfigTest {

@Test
void proxyDisabledByDefault() {
var props = new SimpleOAuthProxyConfig.ProxyProperties();

assertThat(props.isEnabled()).isFalse();
assertThat(props.getHost()).isNull();
assertThat(props.getPort()).isNull();
}

@Test
void proxyPropertiesCanBeSet() {
var props = new SimpleOAuthProxyConfig.ProxyProperties();

props.setEnabled(true);
props.setHost("proxy.example.com");
props.setPort(8080);

assertThat(props.isEnabled()).isTrue();
assertThat(props.getHost()).isEqualTo("proxy.example.com");
assertThat(props.getPort()).isEqualTo(8080);
}

@Test
void createsWebClientWithExplicitProxy() {
var config = new SimpleOAuthProxyConfig();
var props = new SimpleOAuthProxyConfig.ProxyProperties();
props.setEnabled(true);
props.setHost("proxy.example.com");
props.setPort(8080);

WebClient webClient = config.oauth2WebClient(props);

assertThat(webClient).isNotNull();
}

@Test
void createsWebClientWithSystemProxy() {
var config = new SimpleOAuthProxyConfig();
var props = new SimpleOAuthProxyConfig.ProxyProperties();
props.setEnabled(true);
// No host/port - uses system properties

WebClient webClient = config.oauth2WebClient(props);

assertThat(webClient).isNotNull();
}

@Test
void logsCorrectProxyTypeWhenCreatingWebClient() {
var config = new SimpleOAuthProxyConfig();

// Test explicit proxy logging
var explicitProps = new SimpleOAuthProxyConfig.ProxyProperties();
explicitProps.setEnabled(true);
explicitProps.setHost("proxy.example.com");
explicitProps.setPort(3128);

WebClient explicitClient = config.oauth2WebClient(explicitProps);
assertThat(explicitClient).isNotNull();

// Test system proxy logging
var systemProps = new SimpleOAuthProxyConfig.ProxyProperties();
systemProps.setEnabled(true);

WebClient systemClient = config.oauth2WebClient(systemProps);
assertThat(systemClient).isNotNull();
}
}
Loading