Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
knoppiks committed Dec 11, 2024
1 parent 1cd7b2d commit 17e4c9c
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 9 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
- none
- basic
- cert
- oauth2
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

Expand Down
1 change: 1 addition & 0 deletions auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Authn/Authz Util
23 changes: 23 additions & 0 deletions auth/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: fts-auth

services:
keycloak:
image: quay.io/keycloak/keycloak:25.0.6
command: [ "start-dev" ]
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports:
- "8080:8080"
networks: [ "clinical-domain", "research-domain", "trust-center" ]

networks:
clinical-domain:
external: true
name: fts-test_clinical-domain
research-domain:
external: true
name: fts-test_research-domain
trust-center:
external: true
name: fts-test_trust-center
31 changes: 31 additions & 0 deletions clinical-domain-agent/application-auth:oauth2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
server:
port: 9090

projects:
directory: "../.github/test/cd-agent/projects"

security:
auth:
oauth2:
issuer: http://localhost:8080/realms/master

spring:
security:
oauth2:
client:
registration:
agent:
authorizationGrantType: client_credentials
clientId: fts/cd-agent
clientSecret: eA4xj1zFxsVYZGdLah9KnkcmHYDBjojr
provider: keycloak
provider:
keycloak:
issuer-uri: http://localhost:8080/realms/master

test:
webclient:
default:
auth:
oauth2:
registration: agent
15 changes: 15 additions & 0 deletions util/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>

<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-junit-jupiter</artifactId>
Expand Down
22 changes: 22 additions & 0 deletions util/src/main/java/care/smith/fts/util/AgentConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.net.http.HttpClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;

@Configuration
@Import({
Expand Down Expand Up @@ -38,4 +44,20 @@ public HttpClient httpClient() {
public ObjectMapper defaultObjectMapper() {
return new ObjectMapper().registerModule(new JavaTimeModule());
}

@Bean
@ConditionalOnBean(ReactiveClientRegistrationRepository.class)
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
var authorizedClientManager =
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);

var authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials().build();

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
8 changes: 7 additions & 1 deletion util/src/main/java/care/smith/fts/util/WebClientFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import care.smith.fts.util.auth.HttpClientAuth;
import care.smith.fts.util.auth.HttpClientBasicAuth;
import care.smith.fts.util.auth.HttpClientCookieTokenAuth;
import care.smith.fts.util.auth.HttpClientOAuth2Auth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl;
import org.springframework.context.annotation.Import;
Expand All @@ -14,22 +15,25 @@
import org.springframework.web.reactive.function.client.WebClient.Builder;

@Component
@Import({HttpClientBasicAuth.class, HttpClientCookieTokenAuth.class})
@Import({HttpClientBasicAuth.class, HttpClientOAuth2Auth.class, HttpClientCookieTokenAuth.class})
public class WebClientFactory {

private final Builder clientBuilder;
private final WebClientSsl ssl;
private final HttpClientBasicAuth basic;
private final HttpClientOAuth2Auth oauth2;
private final HttpClientCookieTokenAuth token;

public WebClientFactory(
WebClient.Builder clientBuilder,
WebClientSsl ssl,
@Autowired(required = false) HttpClientBasicAuth basic,
@Autowired(required = false) HttpClientOAuth2Auth oauth2,
@Autowired(required = false) HttpClientCookieTokenAuth token) {
this.clientBuilder = clientBuilder;
this.ssl = ssl;
this.basic = basic;
this.oauth2 = oauth2;
this.token = token;
}

Expand All @@ -55,6 +59,8 @@ private void configureAuth(Builder builder, HttpClientAuth.Config auth) {
configureAuth(builder, "basic", basic, auth.basic());
} else if (auth.cookieToken() != null) {
configureAuth(builder, "cookieToken", token, auth.cookieToken());
} else if (auth.oauth2() != null) {
configureAuth(builder, "cookieToken", oauth2, auth.oauth2());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface HttpClientAuth<T> {

record Config(
HttpClientBasicAuth.Config basic,
HttpClientOAuth2Auth.Config oauth2,
HttpClientCookieTokenAuth.Config cookieToken,
Object none) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package care.smith.fts.util.auth;

import care.smith.fts.util.auth.HttpClientOAuth2Auth.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient.Builder;

/** OAuth2 Authentication using oauth2 client credentials flow. */
@Slf4j
@Component
@ConditionalOnBean(ReactiveOAuth2AuthorizedClientManager.class)
public class HttpClientOAuth2Auth implements HttpClientAuth<Config> {

private final ReactiveOAuth2AuthorizedClientManager clientManager;

public HttpClientOAuth2Auth(ReactiveOAuth2AuthorizedClientManager clientManager) {
this.clientManager = clientManager;
}

public record Config(String registration) {}

@Override
public void configure(Config config, Builder builder) {
log.debug(
"Configuring oauth2 client, registration '{}', clientManager: {}",
config.registration(),
clientManager);
var filter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager);
filter.setDefaultClientRegistrationId(config.registration());
builder.filter(filter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ default ServerHttpSecurity filter(Endpoint endpoint, ServerHttpSecurity http) {
ReactiveUserDetailsService configureUsers();

record AuthMethod(
HttpServerClientCertAuth clientCert, HttpServerBasicAuth basic, HttpServerNoneAuth none) {}
HttpServerClientCertAuth clientCert,
HttpServerBasicAuth basic,
HttpServerOAuth2Auth oauth2,
HttpServerNoneAuth none) {}

AuthMethod NONE = new AuthMethod(null, null, HttpServerNoneAuth.NONE);
AuthMethod NONE = new AuthMethod(null, null, null, HttpServerNoneAuth.NONE);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package care.smith.fts.util.auth;

import static org.springframework.security.oauth2.jwt.ReactiveJwtDecoders.fromIssuerLocation;

import care.smith.fts.util.auth.HttpServerAuthConfig.Endpoint;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;

public record HttpServerOAuth2Auth(String issuer) implements HttpServerAuthMethod {

@Override
public ServerHttpSecurity configure(ServerHttpSecurity http) {
return http.oauth2ResourceServer(
oauth2 -> oauth2.jwt(jwt -> jwt.jwtDecoder(fromIssuerLocation(issuer))));
}

@Override
public ServerHttpSecurity filter(Endpoint endpoint, ServerHttpSecurity http) {
return http.authorizeExchange(
exchange -> exchange.pathMatchers(endpoint.path()).hasAuthority(endpoint.role()));
}

@Override
public ReactiveUserDetailsService configureUsers() {
// Since it's client credentials, we don't configure users explicitly here.
return null;
}

@Override
public String toString() {
return "OAuth2";
}
}
10 changes: 6 additions & 4 deletions util/src/test/java/care/smith/fts/util/WebClientFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import care.smith.fts.util.auth.HttpClientAuth.Config;
import care.smith.fts.util.auth.HttpClientBasicAuth;
import care.smith.fts.util.auth.HttpClientCookieTokenAuth;
import care.smith.fts.util.auth.HttpClientOAuth2Auth;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
Expand All @@ -28,8 +29,9 @@ class WebClientFactoryTest {
void setUp(
@Autowired WebClientSsl ssl,
@Autowired HttpClientBasicAuth basic,
@Autowired HttpClientOAuth2Auth oauth2,
@Autowired HttpClientCookieTokenAuth token) {
factory = new WebClientFactory(builder(), ssl, basic, token);
factory = new WebClientFactory(builder(), ssl, basic, oauth2, token);
}

@Test
Expand All @@ -56,7 +58,7 @@ void createWithMultipleAuthBasicIsTaken() {
var basic = Mockito.mock(HttpClientBasicAuth.class);
var token = Mockito.mock(HttpClientCookieTokenAuth.class);
Builder builder = builder();
factory = new WebClientFactory(builder, null, basic, token);
factory = new WebClientFactory(builder, null, basic, null, token);

var basicConf = new HttpClientBasicAuth.Config("user-1505512", "pwd-15054518");
var tokenConf = new HttpClientCookieTokenAuth.Config("token-152510");
Expand All @@ -70,7 +72,7 @@ void createWithMultipleAuthBasicIsTaken() {
@Test
void createWithBasicAuthMissingImplementation(
@Autowired WebClientSsl ssl, @Autowired HttpClientCookieTokenAuth token) {
factory = new WebClientFactory(builder(), ssl, null, token);
factory = new WebClientFactory(builder(), ssl, null, null, token);

var auth = new HttpClientBasicAuth.Config("user-144512", "pwd-144538");
var config = new HttpClientConfig("http://localhost", new Config(auth, null));
Expand All @@ -88,7 +90,7 @@ void createWithTokenAuth() {
@Test
void createWithTokenAuthMissingImplementation(
@Autowired WebClientSsl ssl, @Autowired HttpClientBasicAuth basic) {
factory = new WebClientFactory(builder(), ssl, basic, null);
factory = new WebClientFactory(builder(), ssl, basic, null, null);

var auth = new HttpClientCookieTokenAuth.Config("token-146520");
var config = new HttpClientConfig("http://localhost", new Config(null, auth));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ void multipleAuthMethodsThrow() {
var config = new HttpServerAuthConfig();
config.setAuth(
new AuthMethod(
null, new HttpServerBasicAuth(List.of()), new HttpServerNoneAuth()));
null,
new HttpServerBasicAuth(List.of()),
new HttpServerOAuth2Auth(""),
new HttpServerNoneAuth()));

assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(config::userDetailsService)
Expand All @@ -33,7 +36,7 @@ void multipleAuthMethodsThrow() {
@Test
void noAuthMethodDefaultsToNone() {
var config = new HttpServerAuthConfig();
config.setAuth(new AuthMethod(null, null, null));
config.setAuth(new AuthMethod(null, null, null, null));

var reactiveUserDetailsService = config.userDetailsService();

Expand Down

0 comments on commit 17e4c9c

Please sign in to comment.