Skip to content

Commit a3503ab

Browse files
authored
Store token into caffeine cache (#631)
Signed-off-by: Valentin Delaye <jonesbusy@users.noreply.github.com>
1 parent 506a192 commit a3503ab

8 files changed

Lines changed: 270 additions & 63 deletions

File tree

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<apache.common-compress.version>1.28.0</apache.common-compress.version>
6161
<zstd-jni.version>1.5.7-7</zstd-jni.version>
6262
<bcprov-jdk18on.version>1.83</bcprov-jdk18on.version>
63+
<caffeine.version>3.2.3</caffeine.version>
6364

6465
<!-- Test dependencies version -->
6566
<logback.version>1.5.32</logback.version>
@@ -120,6 +121,11 @@
120121
<artifactId>logback-classic</artifactId>
121122
<version>${logback.version}</version>
122123
</dependency>
124+
<dependency>
125+
<groupId>com.github.ben-manes.caffeine</groupId>
126+
<artifactId>caffeine</artifactId>
127+
<version>${caffeine.version}</version>
128+
</dependency>
123129
<dependency>
124130
<groupId>com.github.luben</groupId>
125131
<artifactId>zstd-jni</artifactId>
@@ -185,6 +191,10 @@
185191

186192
<!-- Dependencies -->
187193
<dependencies>
194+
<dependency>
195+
<groupId>com.github.ben-manes.caffeine</groupId>
196+
<artifactId>caffeine</artifactId>
197+
</dependency>
188198
<dependency>
189199
<groupId>com.github.luben</groupId>
190200
<artifactId>zstd-jni</artifactId>

src/main/java/land/oras/Registry.java

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public Tags getTags(ContainerRef containerRef) {
200200
}
201201
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getTagsPath(this)));
202202
HttpClient.ResponseWrapper<String> response = client.get(
203-
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(this, ref), authProvider);
203+
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(ref), authProvider);
204204
logResponse(response);
205205
handleError(response);
206206
return JsonUtils.fromJson(response.response(), Tags.class)
@@ -215,7 +215,7 @@ public Tags getTags(ContainerRef containerRef, int n, @Nullable String last) {
215215
}
216216
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getTagsPath(this, n, last)));
217217
HttpClient.ResponseWrapper<String> response = client.get(
218-
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(this, ref), authProvider);
218+
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(ref), authProvider);
219219
logResponse(response);
220220
handleError(response);
221221
return JsonUtils.fromJson(response.response(), Tags.class)
@@ -232,7 +232,7 @@ && getRegistriesConf().isInsecure(ContainerRef.parse(registry).forRegistry(regis
232232
ContainerRef ref = ContainerRef.parse("default").forRegistry(this);
233233
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getRepositoriesPath(this)));
234234
HttpClient.ResponseWrapper<String> response = client.get(
235-
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(this, ref), authProvider);
235+
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(ref), authProvider);
236236
logResponse(response);
237237
handleError(response);
238238
return JsonUtils.fromJson(response.response(), Repositories.class);
@@ -249,7 +249,7 @@ public Referrers getReferrers(ContainerRef containerRef, @Nullable ArtifactType
249249
}
250250
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getReferrersPath(this, artifactType)));
251251
HttpClient.ResponseWrapper<String> response = client.get(
252-
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE), Scopes.of(this, ref), authProvider);
252+
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE), Scopes.of(ref), authProvider);
253253
logResponse(response);
254254
handleError(response);
255255
return JsonUtils.fromJson(response.response(), Referrers.class);
@@ -266,7 +266,7 @@ public void deleteManifest(ContainerRef containerRef) {
266266
return;
267267
}
268268
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
269-
HttpClient.ResponseWrapper<String> response = client.delete(uri, Map.of(), Scopes.of(this, ref), authProvider);
269+
HttpClient.ResponseWrapper<String> response = client.delete(uri, Map.of(), Scopes.of(ref), authProvider);
270270
logResponse(response);
271271
handleError(response);
272272
}
@@ -297,7 +297,7 @@ public Manifest pushManifest(ContainerRef containerRef, Manifest manifest) {
297297
uri,
298298
manifestData,
299299
Map.of(Const.CONTENT_TYPE_HEADER, Const.DEFAULT_MANIFEST_MEDIA_TYPE),
300-
Scopes.of(this, ref),
300+
Scopes.of(ref),
301301
authProvider);
302302
logResponse(response);
303303
handleError(response);
@@ -336,7 +336,7 @@ public Index pushIndex(ContainerRef containerRef, Index index) {
336336
uri,
337337
indexData,
338338
Map.of(Const.CONTENT_TYPE_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE),
339-
Scopes.of(this, ref),
339+
Scopes.of(ref),
340340
authProvider);
341341
logResponse(response);
342342
handleError(response);
@@ -354,7 +354,7 @@ public void deleteBlob(ContainerRef containerRef) {
354354
return;
355355
}
356356
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsPath(this)));
357-
HttpClient.ResponseWrapper<String> response = client.delete(uri, Map.of(), Scopes.of(this, ref), authProvider);
357+
HttpClient.ResponseWrapper<String> response = client.delete(uri, Map.of(), Scopes.of(ref), authProvider);
358358
logResponse(response);
359359
handleError(response);
360360
}
@@ -475,7 +475,7 @@ public Layer pushBlob(ContainerRef containerRef, Path blob, Map<String, String>
475475
uri,
476476
Map.of(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
477477
blob,
478-
Scopes.of(this, ref),
478+
Scopes.of(ref),
479479
authProvider);
480480
logResponse(response);
481481

@@ -501,7 +501,7 @@ public Layer pushBlob(ContainerRef containerRef, Path blob, Map<String, String>
501501
uploadURI,
502502
Map.of(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
503503
blob,
504-
Scopes.of(this, ref),
504+
Scopes.of(ref),
505505
authProvider);
506506
if (response.statusCode() == 201) {
507507
LOG.debug("Successful push: {}", response.response());
@@ -534,7 +534,7 @@ public Layer pushBlob(ContainerRef ref, long size, Supplier<InputStream> stream,
534534
uri,
535535
new byte[0],
536536
Map.of(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
537-
Scopes.of(this, containerRef),
537+
Scopes.of(containerRef),
538538
authProvider);
539539
logResponse(response);
540540
if (response.statusCode() != 202) {
@@ -554,7 +554,7 @@ public Layer pushBlob(ContainerRef ref, long size, Supplier<InputStream> stream,
554554
size,
555555
Map.of(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
556556
stream,
557-
Scopes.of(this, containerRef),
557+
Scopes.of(containerRef),
558558
authProvider);
559559
logResponse(response);
560560
if (response.statusCode() == 201) {
@@ -586,7 +586,7 @@ public Layer pushBlob(ContainerRef containerRef, byte[] data) {
586586
uri,
587587
data,
588588
Map.of(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
589-
Scopes.of(this, ref),
589+
Scopes.of(ref),
590590
authProvider);
591591
logResponse(response);
592592

@@ -611,7 +611,7 @@ public Layer pushBlob(ContainerRef containerRef, byte[] data) {
611611
uploadURI,
612612
data,
613613
Map.of(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
614-
Scopes.of(this, ref),
614+
Scopes.of(ref),
615615
authProvider);
616616
if (response.statusCode() == 201) {
617617
LOG.debug("Successful push: {}", response.response());
@@ -643,7 +643,7 @@ private HttpClient.ResponseWrapper<String> headBlob(ContainerRef containerRef) {
643643
HttpClient.ResponseWrapper<String> response = client.head(
644644
uri,
645645
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
646-
Scopes.of(this, containerRef),
646+
Scopes.of(containerRef),
647647
authProvider);
648648
logResponse(response);
649649
return response;
@@ -664,7 +664,7 @@ public byte[] getBlob(ContainerRef containerRef) {
664664
HttpClient.ResponseWrapper<String> response = client.get(
665665
uri,
666666
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
667-
Scopes.of(this, ref),
667+
Scopes.of(ref),
668668
authProvider);
669669
logResponse(response);
670670
handleError(response);
@@ -685,7 +685,7 @@ public void fetchBlob(ContainerRef containerRef, Path path) {
685685
uri,
686686
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
687687
path,
688-
Scopes.of(this, ref),
688+
Scopes.of(ref),
689689
authProvider);
690690
logResponse(response);
691691
handleError(response);
@@ -702,7 +702,7 @@ public InputStream fetchBlob(ContainerRef containerRef) {
702702
HttpClient.ResponseWrapper<InputStream> response = client.download(
703703
uri,
704704
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
705-
Scopes.of(this, ref),
705+
Scopes.of(ref),
706706
authProvider);
707707
logResponse(response);
708708
handleError(response);
@@ -810,8 +810,8 @@ boolean exists(ContainerRef containerRef) {
810810
return asInsecure().exists(containerRef);
811811
}
812812
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
813-
HttpClient.ResponseWrapper<String> response = client.head(
814-
uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(this, ref), authProvider);
813+
HttpClient.ResponseWrapper<String> response =
814+
client.head(uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
815815
logResponse(response);
816816
return response.statusCode() == 200;
817817
}
@@ -827,11 +827,11 @@ private HttpClient.ResponseWrapper<String> getManifestResponse(ContainerRef cont
827827
return asInsecure().getManifestResponse(containerRef);
828828
}
829829
URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
830-
HttpClient.ResponseWrapper<String> response = client.head(
831-
uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(this, ref), authProvider);
830+
HttpClient.ResponseWrapper<String> response =
831+
client.head(uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
832832
logResponse(response);
833833
handleError(response);
834-
return client.get(uri, Map.of("Accept", Const.MANIFEST_ACCEPT_TYPE), Scopes.of(this, ref), authProvider);
834+
return client.get(uri, Map.of("Accept", Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
835835
}
836836

837837
private void validateDockerContentDigest(HttpClient.ResponseWrapper<String> response, byte[] data) {
@@ -981,8 +981,8 @@ ResolvedRegistry getResolvedHeaders(ContainerRef containerRef) {
981981
ContainerRef ref = containerRef.forRegistry(this);
982982
URI uri = URI.create(
983983
"%s://%s".formatted(getScheme(), ref.forRegistry(this).getManifestsPath(this)));
984-
HttpClient.ResponseWrapper<String> response = client.head(
985-
uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(this, ref), authProvider);
984+
HttpClient.ResponseWrapper<String> response =
985+
client.head(uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
986986
logResponse(response);
987987
handleError(response);
988988
return new ResolvedRegistry(ref.getRegistry(), response.headers());

src/main/java/land/oras/auth/HttpClient.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ public <T> TokenResponse refreshToken(
406406
String error = matcher.group(5);
407407

408408
// Add server scope to existing scopes
409-
Scopes newScopes = scopes.withNewScope(scope);
409+
Scopes newScopes = scopes.withNewScope(scope).withService(service);
410410
LOG.debug("New scopes with server: {}", newScopes.getScopes());
411411

412412
LOG.debug("WWW-Authenticate header: realm={}, service={}, scope={}, error={}", realm, service, scope, error);
@@ -437,7 +437,10 @@ public <T> TokenResponse refreshToken(
437437
? "<redacted" // Replace value with ****
438438
: entry.getValue())));
439439

440-
return JsonUtils.fromJson(responseWrapper.response(), TokenResponse.class);
440+
// Put in the cache
441+
TokenResponse token = JsonUtils.fromJson(responseWrapper.response(), TokenResponse.class);
442+
TokenCache.put(newScopes, token);
443+
return token;
441444
}
442445

443446
static boolean isSameOrigin(URI uri1, URI uri2) {
@@ -493,10 +496,23 @@ private <T> ResponseWrapper<T> executeRequest(
493496
LOG.debug("Existing scopes: {}", scopes.getScopes());
494497
LOG.debug("New scopes: {}", newScopes.getScopes());
495498

496-
// Add authentication header if any
499+
// Check if token is present and reuse auth instead of passing auth provider
500+
TokenResponse cachedToken = TokenCache.get(newScopes);
501+
if (cachedToken == null) {
502+
LOG.trace("No cached token found for scopes: {}", newScopes);
503+
} else {
504+
LOG.trace("Cached token for scopes: {}", newScopes);
505+
}
506+
507+
// Add authentication header if any (from provider or cached token)
497508
var authHeader = authProvider.getAuthHeader(containerRef);
498-
if (authHeader != null && !authProvider.getAuthScheme().equals(AuthScheme.NONE) && includeAuthHeader) {
509+
if (cachedToken == null
510+
&& authHeader != null
511+
&& !authProvider.getAuthScheme().equals(AuthScheme.NONE)
512+
&& includeAuthHeader) {
499513
builder = builder.header(Const.AUTHORIZATION_HEADER, authHeader);
514+
} else if (cachedToken != null && includeAuthHeader) {
515+
builder = builder.header(Const.AUTHORIZATION_HEADER, "Bearer " + cachedToken.getEffectiveToken());
500516
}
501517
headers.forEach(builder::header);
502518

@@ -667,6 +683,7 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngi
667683
/**
668684
* The token response
669685
* @param token The token
686+
* @param service The service (not on response but on HTTP headers)
670687
* @param access_token The access token
671688
* @param expires_in The expires in
672689
* @param issued_at The issued at
@@ -675,8 +692,23 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngi
675692
public record TokenResponse(
676693
String token,
677694
@Nullable String access_token,
695+
@Nullable String service,
678696
@Nullable Integer expires_in,
679-
@Nullable ZonedDateTime issued_at) {}
697+
@Nullable ZonedDateTime issued_at) {
698+
699+
/**
700+
* Get the effective token
701+
* @return The effective token, which is either the access_token or the token field depending on which one is present
702+
*/
703+
public String getEffectiveToken() {
704+
return access_token != null ? access_token : token;
705+
}
706+
707+
@Override
708+
public String toString() {
709+
return "TokenResponse{" + "expires_in=" + expires_in + ", issued_at=" + issued_at + '}';
710+
}
711+
}
680712

681713
/**
682714
* Builder for the HTTP client

src/main/java/land/oras/auth/ScopeUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ public static List<String> appendRepositoryScope(
5959
}
6060
return cleanScopes(cleaned);
6161
}
62-
6362
/**
6463
* Create a scope for a repository.
6564
* @param ref the container reference

0 commit comments

Comments
 (0)