Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use Several Clients for OAuth2
Browse files Browse the repository at this point in the history
trobanga committed Jan 23, 2025

Verified

This commit was signed with the committer’s verified signature.
trobanga Daniel Hahne
1 parent bbddb2e commit b78b578
Showing 16 changed files with 300 additions and 33 deletions.
246 changes: 237 additions & 9 deletions .github/test/oauth2/import/fts-realm.json
Original file line number Diff line number Diff line change
@@ -145,9 +145,112 @@
},
"clients": [
{
"id": "34ed2b5c-6e6d-4477-bfa2-f9c36f4f6133",
"id": "ee7a80f4-da2f-4bc8-962b-369c9fd36b1c",
"clientId": "fts-client",
"name": "FTS Client",
"name": "FTS CD Client",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84",
"redirectUris": [
"http://localhost:8080/*"
],
"webOrigins": [
"http://localhost:8080"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"access.token.lifespan": "1800",
"client.secret.creation.time": "1737365263",
"backchannel.logout.session.required": "true",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"id": "2787d9e7-4f4a-4443-8efd-b83b392c8a2e",
"name": "Client IP Address",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientAddress",
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientAddress",
"jsonType.label": "String"
}
},
{
"id": "911d7a9a-0015-4e18-8f75-9b29711b23b0",
"name": "Client Host",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientHost",
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientHost",
"jsonType.label": "String"
}
},
{
"id": "08117557-9386-4a7e-b9b2-c5c67305ade9",
"name": "Client ID",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "client_id",
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true",
"claim.name": "client_id",
"jsonType.label": "String"
}
}
],
"defaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"basic",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
},
{
"id": "34ed2b5c-6e6d-4477-bfa2-f9c36f4f6133",
"clientId": "cd-client",
"name": "FTS CD Client",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
@@ -246,39 +349,164 @@
"configure": true,
"manage": true
}
},
{
"id": "f03b64d9-c57b-4061-a781-52e29bf05084",
"clientId": "rd-client",
"name": "FTS RD Client",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84",
"redirectUris": [
"http://localhost:8080/*"
],
"webOrigins": [
"http://localhost:8080"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"access.token.lifespan": "1800",
"client.secret.creation.time": "1737365263",
"backchannel.logout.session.required": "true",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"id": "08f5928a-1673-4d08-bf8d-5fc52b37ea2d",
"name": "Client IP Address",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientAddress",
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientAddress",
"jsonType.label": "String"
}
},
{
"id": "49404a86-a5aa-4dd9-b847-a8474bbd0841",
"name": "Client Host",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientHost",
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientHost",
"jsonType.label": "String"
}
},
{
"id": "32165832-73ef-4a51-8a8c-75310171c9e4",
"name": "Client ID",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "client_id",
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true",
"claim.name": "client_id",
"jsonType.label": "String"
}
}
],
"defaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"basic",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
],
"roles": {
"client": {
"fts-client": [
{
"name": "client"
"name": "cd-client"
},
{
"name": "rd-client"
}
]
}
},
"clientScopeMappings": {
"fts-client": [
{
"client": "fts-client",
"client": "cd-client",
"roles": [
"client"
"cd-client"
]
},
{
"client": "rd-client",
"roles": [
"rd-client"
]
}
]
},
"users": [
{
"username": "service-account-fts-client",
"username": "service-account-cd-client",
"enabled": true,
"serviceAccountClientId": "cd-client",
"clientRoles": {
"fts-client": [
"cd-client"
]
},
"realmRoles": [
"default-roles-cd"
]
},
{
"username": "service-account-rd-client",
"enabled": true,
"serviceAccountClientId": "fts-client",
"serviceAccountClientId": "rd-client",
"clientRoles": {
"fts-client": [
"client"
"rd-client"
]
},
"realmRoles": [
"default-roles-fts"
"default-roles-rd"
]
}
]
2 changes: 1 addition & 1 deletion clinical-domain-agent/application-auth:oauth2.yaml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ spring:
registration:
agent:
authorization-grant-type: client_credentials
client-id: fts-client
client-id: cd-client
client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84
provider: keycloak
provider:
2 changes: 1 addition & 1 deletion clinical-domain-agent/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ management:
security:
endpoints:
- path: /api/v2/**
role: client
role: cd-client

runner:
maxSendConcurrency: 32
2 changes: 1 addition & 1 deletion clinical-domain-agent/src/test/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ test:
security:
endpoints:
- path: /api/v2/**
role: client
role: cd-client

logging.level:
care.smith.fts: DEBUG
2 changes: 1 addition & 1 deletion research-domain-agent/application-auth:oauth2.yaml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ spring:
registration:
agent:
authorization-grant-type: client_credentials
client-id: fts-client
client-id: rd-client
client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84
provider: keycloak
provider:
2 changes: 1 addition & 1 deletion research-domain-agent/src/test/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ test:
security:
endpoints:
- path: /api/v2/**
role: client
role: rd-client

logging.level:
care.smith.fts: DEBUG
Original file line number Diff line number Diff line change
@@ -35,6 +35,9 @@ public WebClient webClient(String baseUrl) {
}

public WebClient webClient(String baseUrl, String clientName) {

log.debug("TestWebClientFactory baseurl: {}, clientName: {}", baseUrl, clientName);

return config
.findConfigurationEntry(clientName)
.map(c -> factory.create(new HttpClientConfig(baseUrl, c.auth(), c.ssl())))
13 changes: 9 additions & 4 deletions trust-center-agent/application-auth:oauth2.yaml
Original file line number Diff line number Diff line change
@@ -9,9 +9,14 @@ spring:
oauth2:
client:
registration:
agent:
cd-agent:
authorization-grant-type: client_credentials
client-id: fts-client
client-id: cd-client
client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84
provider: keycloak
rd-agent:
authorization-grant-type: client_credentials
client-id: rd-client
client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84
provider: keycloak
provider:
@@ -23,8 +28,8 @@ test:
cd-agent:
auth:
oauth2:
registration: agent
registration: cd-agent
rd-agent:
auth:
oauth2:
registration: agent
registration: rd-agent
4 changes: 2 additions & 2 deletions trust-center-agent/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -13,9 +13,9 @@ management:
security:
endpoints:
- path: /api/v2/cd/**
role: client
role: cd-client
- path: /api/v2/rd/**
role: client
role: rd-client

deIdentification:
keystoreUrl: redis://keystore:6379
Original file line number Diff line number Diff line change
@@ -50,9 +50,12 @@ class DeIdentificationControllerIT extends BaseIT {
private static WebClient rdClient;

@BeforeAll
static void setUp(@LocalServerPort int port, @Autowired TestWebClientFactory factory) {
cdClient = factory.webClient("https://localhost:" + port, "cd-agent");
rdClient = factory.webClient("https://localhost:" + port, "rd-agent");
static void setUp(
@LocalServerPort int cd_port,
@LocalServerPort int rd_port,
@Autowired TestWebClientFactory factory) {
cdClient = factory.webClient("https://localhost:" + cd_port, "cd-agent");
rdClient = factory.webClient("https://localhost:" + rd_port, "rd-agent");
}

@Test
4 changes: 2 additions & 2 deletions trust-center-agent/src/test/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -23,9 +23,9 @@ logging.level:
security:
endpoints:
- path: /api/v2/cd/**
role: client
role: cd-client
- path: /api/v2/rd/**
role: client
role: rd-client

spring:
main:
Original file line number Diff line number Diff line change
@@ -4,11 +4,13 @@

import ca.uhn.fhir.context.FhirContext;
import care.smith.fts.util.auth.HttpServerAuthConfig;
import care.smith.fts.util.auth.OAuth2ConfigurationExistsCondition;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.net.http.HttpClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
@@ -47,7 +49,7 @@ public ObjectMapper defaultObjectMapper() {
}

@Bean
@ConditionalOnProperty(name = "spring.security.oauth2.client.registration.agent.provider")
@Conditional(OAuth2ConfigurationExistsCondition.class)
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

import care.smith.fts.util.auth.HttpClientOAuth2Auth.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.stereotype.Component;
@@ -11,8 +11,7 @@
/** OAuth2 Authentication using oauth2 client credentials flow. */
@Slf4j
@Component
// @ConditionalOnProperty(prefix = "spring.security.oauth2")
@ConditionalOnProperty(name = "spring.security.oauth2.client.registration.agent.provider")
@Conditional(OAuth2ConfigurationExistsCondition.class)
public class HttpClientOAuth2Auth implements HttpClientAuth<Config> {

private final ReactiveOAuth2AuthorizedClientManager clientManager;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package care.smith.fts.util.auth;

import java.util.Arrays;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OAuth2ConfigurationExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableEnvironment env = (ConfigurableEnvironment) context.getEnvironment();
return env.getPropertySources().stream()
.filter(ps -> ps instanceof EnumerablePropertySource)
.map(ps -> (EnumerablePropertySource<?>) ps)
.flatMap(ps -> Arrays.stream(ps.getPropertyNames()))
.anyMatch(prop -> prop.startsWith("spring.security.oauth2"));
}
}
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ void noUserDetailsForNoneAuth() {
var config = new HttpServerAuthConfig();

var reactiveUserDetailsService = config.userDetailsService();

assertThat(reactiveUserDetailsService).isNull();
}

Original file line number Diff line number Diff line change
@@ -3,24 +3,32 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.web.server.WebFilter;

@Slf4j
class HttpServerOAuth2AuthTest {
HttpServerOAuth2Auth auth = new HttpServerOAuth2Auth("issuer", "clientId");

@Test
void configure() {
var security = ServerHttpSecurity.http();
var result = auth.configure(security).build();

assertThat(result.getWebFilters().collectList().block())
var chain = auth.configure(security).build();

var filters = chain.getWebFilters().collectList().block();

assertThat(filters)
.filteredOn(filter -> filter instanceof AuthenticationWebFilter)
.hasSize(1);
.isNotEmpty();
}

@Test

0 comments on commit b78b578

Please sign in to comment.