-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45131 from michalvavrik/feature/oidc-client-jwt-b…
…earer-auth OIDC and OIDC Client: Support JWT bearer client authentication using client assertion loaded from filesystem
- Loading branch information
Showing
36 changed files
with
667 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
...-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/ClientAssertionProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package io.quarkus.oidc.common.runtime; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
|
||
import org.eclipse.microprofile.jwt.Claims; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.vertx.core.Handler; | ||
import io.vertx.core.Vertx; | ||
import io.vertx.core.json.JsonObject; | ||
|
||
public final class ClientAssertionProvider implements Closeable { | ||
|
||
private record ClientAssertion(String bearerToken, long expiresAt, long timerId) { | ||
private boolean isInvalid() { | ||
final long nowSecs = System.currentTimeMillis() / 1000; | ||
return nowSecs > expiresAt; | ||
} | ||
} | ||
|
||
private static final Logger LOG = Logger.getLogger(ClientAssertionProvider.class); | ||
private final Vertx vertx; | ||
private final Path bearerTokenPath; | ||
private volatile ClientAssertion clientAssertion; | ||
|
||
public ClientAssertionProvider(Vertx vertx, Path bearerTokenPath) { | ||
this.vertx = vertx; | ||
this.bearerTokenPath = bearerTokenPath; | ||
this.clientAssertion = loadFromFileSystem(); | ||
} | ||
|
||
public String getClientAssertion() { | ||
ClientAssertion clientAssertion = this.clientAssertion; | ||
if (isInvalid(clientAssertion)) { | ||
clientAssertion = loadClientAssertion(); | ||
} | ||
return clientAssertion == null ? null : clientAssertion.bearerToken; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
cancelRefresh(); | ||
clientAssertion = null; | ||
} | ||
|
||
private synchronized ClientAssertion loadClientAssertion() { | ||
if (isInvalid(clientAssertion)) { | ||
cancelRefresh(); | ||
clientAssertion = loadFromFileSystem(); | ||
} | ||
return clientAssertion; | ||
} | ||
|
||
private long scheduleRefresh(long expiresAt) { | ||
// in K8 and OCP, tokens are proactively rotated at 80 % of their TTL | ||
long delay = (long) (expiresAt * 0.85); | ||
return vertx.setTimer(delay, new Handler<Long>() { | ||
@Override | ||
public void handle(Long ignored) { | ||
ClientAssertionProvider.this.clientAssertion = loadFromFileSystem(); | ||
} | ||
}); | ||
} | ||
|
||
private void cancelRefresh() { | ||
if (clientAssertion != null) { | ||
vertx.cancelTimer(clientAssertion.timerId); | ||
} | ||
} | ||
|
||
private ClientAssertion loadFromFileSystem() { | ||
if (Files.exists(bearerTokenPath)) { | ||
try { | ||
String bearerToken = Files.readString(bearerTokenPath).trim(); | ||
Long expiresAt = getExpiresAtFromExpClaim(bearerToken); | ||
if (expiresAt != null) { | ||
return new ClientAssertion(bearerToken, expiresAt, scheduleRefresh(expiresAt)); | ||
} else { | ||
LOG.error("Bearer token or its expiry claim is invalid"); | ||
} | ||
} catch (IOException e) { | ||
LOG.error("Failed to read file with a bearer token at path: " + bearerTokenPath, e); | ||
} | ||
} else { | ||
LOG.warn("Cannot find a file with a bearer token at path: " + bearerTokenPath); | ||
} | ||
return null; | ||
} | ||
|
||
private static boolean isInvalid(ClientAssertion clientAssertion) { | ||
return clientAssertion == null || clientAssertion.isInvalid(); | ||
} | ||
|
||
private static Long getExpiresAtFromExpClaim(String bearerToken) { | ||
JsonObject claims = OidcCommonUtils.decodeJwtContent(bearerToken); | ||
if (claims == null || !claims.containsKey(Claims.exp.name())) { | ||
return null; | ||
} | ||
try { | ||
return claims.getLong(Claims.exp.name()); | ||
} catch (IllegalArgumentException ex) { | ||
LOG.debug("Bearer token expiry claim can not be converted to Long"); | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.