Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ The subject of the client assertion JWT. Optional. The default is the client ID.

### `rest.auth.oauth2.client-assertion.jwt.audience`

The audience of the client assertion JWT. Optional. The default is the token endpoint.
The audience of the client assertion JWT. Optional. The default is the token endpoint. Can be a single audience or a comma-separated list of audiences.

### `rest.auth.oauth2.client-assertion.jwt.token-lifespan`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -66,9 +67,37 @@ public interface ClientAssertionConfig {
@WithName(SUBJECT)
Optional<Subject> getSubject();

/** The audience of the client assertion JWT. Optional. The default is the token endpoint. */
/**
* The audience of the client assertion JWT. Optional. The default is the token endpoint. Can be a
* single audience or a comma-separated list of audiences.
*/
@WithName(AUDIENCE)
Optional<Audience> getAudience();
Optional<String> getAudience();
Comment thread
adutra marked this conversation as resolved.
Outdated

/**
* Parse the audience string into a list of Audience objects. If no audience is configured,
* returns an empty list.
*/
default List<Audience> getAudienceList() {
if (!getAudience().isPresent()) {
return new ArrayList<>();
}

String audienceStr = getAudience().get().trim();
if (audienceStr.isEmpty()) {
return new ArrayList<>();
}

List<Audience> audiences = new ArrayList<>();
String[] parts = audienceStr.split(",");
for (String part : parts) {
String trimmed = part.trim();
if (!trimmed.isEmpty()) {
audiences.add(new Audience(trimmed));
}
}
return audiences;
}

/** The expiration time of the client assertion JWT. Optional. The default is 5 minutes. */
@WithName(TOKEN_LIFESPAN)
Expand Down Expand Up @@ -166,10 +195,10 @@ default void validate() {
}

default Map<String, String> asMap() {
Map<String, String> properties = new HashMap<>();
Map<String, String> properties = new HashMap<String, String>();
getIssuer().ifPresent(i -> properties.put(PREFIX + '.' + ISSUER, i.getValue()));
getSubject().ifPresent(s -> properties.put(PREFIX + '.' + SUBJECT, s.getValue()));
getAudience().ifPresent(a -> properties.put(PREFIX + '.' + AUDIENCE, a.getValue()));
getAudience().ifPresent(a -> properties.put(PREFIX + '.' + AUDIENCE, a));
properties.put(PREFIX + '.' + TOKEN_LIFESPAN, getTokenLifespan().toString());
getAlgorithm().ifPresent(a -> properties.put(PREFIX + '.' + ALGORITHM, a.getName()));
getKeyId().ifPresent(k -> properties.put(PREFIX + '.' + KEY_ID, k));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,18 +231,18 @@ private JWTAssertionDetails createJwtAssertionDetails(URI tokenEndpoint) {
getConfig().getClientAssertionConfig().getSubject().isPresent()
? getConfig().getClientAssertionConfig().getSubject().get()
: new Subject(getConfig().getBasicConfig().getClientId().orElseThrow().getValue());
Audience audience =
getConfig().getClientAssertionConfig().getAudience().isPresent()
? getConfig().getClientAssertionConfig().getAudience().get()
: new Audience(tokenEndpoint);
List<Audience> audiences = getConfig().getClientAssertionConfig().getAudienceList();
if (audiences.isEmpty()) {
audiences = List.of(new Audience(tokenEndpoint));
}
Instant issuedAt = getRuntime().getClock().instant();
Instant expiration = issuedAt.plus(getConfig().getClientAssertionConfig().getTokenLifespan());
@SuppressWarnings({"rawtypes", "unchecked"})
Map<String, Object> extraClaims = (Map) getConfig().getClientAssertionConfig().getExtraClaims();
return new JWTAssertionDetails(
issuer,
subject,
List.of(audience),
audiences,
Date.from(expiration),
Date.from(issuedAt),
Date.from(issuedAt),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,37 @@ void testKeyIdPresent() {
smallRyeConfig.getConfigMapping(ClientAssertionConfig.class, PREFIX);
assertThat(config.getKeyId()).hasValue("my-key-123");
}

@Test
void testAudienceListSingleValue() {
Map<String, String> properties =
Map.of(PREFIX + '.' + ClientAssertionConfig.AUDIENCE, "https://example.com");
SmallRyeConfig smallRyeConfig =
new SmallRyeConfigBuilder()
.withMapping(ClientAssertionConfig.class, PREFIX)
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
.build();
ClientAssertionConfig config =
smallRyeConfig.getConfigMapping(ClientAssertionConfig.class, PREFIX);
assertThat(config.getAudienceList()).hasSize(1);
assertThat(config.getAudienceList().get(0).getValue()).isEqualTo("https://example.com");
}

@Test
void testAudienceListMultipleValues() {
Map<String, String> properties =
Map.of(
PREFIX + '.' + ClientAssertionConfig.AUDIENCE,
"https://auth1.example.com,https://auth2.example.com");
SmallRyeConfig smallRyeConfig =
new SmallRyeConfigBuilder()
.withMapping(ClientAssertionConfig.class, PREFIX)
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
.build();
ClientAssertionConfig config =
smallRyeConfig.getConfigMapping(ClientAssertionConfig.class, PREFIX);
assertThat(config.getAudienceList()).hasSize(2);
assertThat(config.getAudienceList().get(0).getValue()).isEqualTo("https://auth1.example.com");
assertThat(config.getAudienceList().get(1).getValue()).isEqualTo("https://auth2.example.com");
}
}