Skip to content

Commit 784cb11

Browse files
committed
Cache JWT tokens instead of generating per request
1 parent 0bccefd commit 784cb11

File tree

2 files changed

+48
-6
lines changed

2 files changed

+48
-6
lines changed

core/trino-main/src/main/java/io/trino/server/InternalAuthenticationManager.java

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@
3030

3131
import javax.crypto.SecretKey;
3232

33+
import java.time.Instant;
3334
import java.time.ZonedDateTime;
3435
import java.util.Date;
36+
import java.util.concurrent.atomic.AtomicReference;
37+
import java.util.function.Function;
38+
import java.util.function.Supplier;
3539

3640
import static io.airlift.http.client.Request.Builder.fromRequest;
3741
import static io.jsonwebtoken.security.Keys.hmacShaKeyFor;
@@ -41,18 +45,23 @@
4145
import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
4246
import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED;
4347
import static java.nio.charset.StandardCharsets.UTF_8;
48+
import static java.time.temporal.ChronoUnit.MINUTES;
4449
import static java.util.Objects.requireNonNull;
4550

4651
public class InternalAuthenticationManager
4752
implements HttpRequestFilter
4853
{
4954
private static final Logger log = Logger.get(InternalAuthenticationManager.class);
55+
private static final Supplier<Instant> DEFAULT_EXPIRATION_SUPPLIER = () -> ZonedDateTime.now().plusMinutes(6).toInstant();
56+
// Leave a 5 minute buffer to allow for clock skew and GC pauses
57+
private static final Function<Instant, Instant> TOKEN_REUSE_THRESHOLD = instant -> instant.minus(5, MINUTES);
5058

5159
private static final String TRINO_INTERNAL_BEARER = "X-Trino-Internal-Bearer";
5260

5361
private final SecretKey hmac;
5462
private final String nodeId;
5563
private final JwtParser jwtParser;
64+
private final AtomicReference<InternalToken> currentToken;
5665

5766
@Inject
5867
public InternalAuthenticationManager(InternalCommunicationConfig internalCommunicationConfig, SecurityConfig securityConfig, NodeInfo nodeInfo)
@@ -84,6 +93,7 @@ public InternalAuthenticationManager(String sharedSecret, String nodeId)
8493
this.hmac = hmacShaKeyFor(Hashing.sha256().hashString(sharedSecret, UTF_8).asBytes());
8594
this.nodeId = nodeId;
8695
this.jwtParser = newJwtParserBuilder().verifyWith(hmac).build();
96+
this.currentToken = new AtomicReference<>(createJwt());
8797
}
8898

8999
public static boolean isInternalRequest(ContainerRequestContext request)
@@ -118,17 +128,34 @@ public void handleInternalRequest(ContainerRequestContext request)
118128
public Request filterRequest(Request request)
119129
{
120130
return fromRequest(request)
121-
.addHeader(TRINO_INTERNAL_BEARER, generateJwt())
131+
.addHeader(TRINO_INTERNAL_BEARER, getOrGenerateJwt())
122132
.build();
123133
}
124134

125-
private String generateJwt()
135+
private String getOrGenerateJwt()
126136
{
127-
return newJwtBuilder()
137+
InternalToken token = currentToken.get();
138+
if (token.isExpired()) {
139+
InternalToken newToken = createJwt();
140+
if (currentToken.compareAndSet(token, newToken)) {
141+
token = newToken;
142+
}
143+
else {
144+
// Another thread already generated a new token
145+
token = currentToken.get();
146+
}
147+
}
148+
return token.token();
149+
}
150+
151+
private InternalToken createJwt()
152+
{
153+
Instant expiration = DEFAULT_EXPIRATION_SUPPLIER.get();
154+
return new InternalToken(expiration, newJwtBuilder()
128155
.signWith(hmac)
129156
.subject(nodeId)
130-
.expiration(Date.from(ZonedDateTime.now().plusMinutes(5).toInstant()))
131-
.compact();
157+
.expiration(Date.from(expiration))
158+
.compact());
132159
}
133160

134161
private String parseJwt(String jwt)
@@ -138,4 +165,18 @@ private String parseJwt(String jwt)
138165
.getPayload()
139166
.getSubject();
140167
}
168+
169+
private record InternalToken(Instant expiration, String token)
170+
{
171+
public InternalToken
172+
{
173+
expiration = TOKEN_REUSE_THRESHOLD.apply(requireNonNull(expiration, "expiration is null"));
174+
requireNonNull(token, "token is null");
175+
}
176+
177+
public boolean isExpired()
178+
{
179+
return Instant.now().isAfter(expiration);
180+
}
181+
}
141182
}

core/trino-main/src/main/java/io/trino/server/InternalCommunicationModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package io.trino.server;
1515

1616
import com.google.inject.Binder;
17+
import com.google.inject.Scopes;
1718
import com.google.inject.multibindings.Multibinder;
1819
import io.airlift.configuration.AbstractConfigurationAwareModule;
1920
import io.airlift.discovery.client.ForDiscoveryClient;
@@ -52,7 +53,7 @@ protected void setup(Binder binder)
5253
}
5354
discoveryFilterBinder.addBinding().to(InternalAuthenticationManager.class);
5455
configBinder(binder).bindConfigDefaults(HttpClientConfig.class, ForDiscoveryClient.class, config -> configureClient(config, internalCommunicationConfig));
55-
binder.bind(InternalAuthenticationManager.class);
56+
binder.bind(InternalAuthenticationManager.class).in(Scopes.SINGLETON);
5657
}
5758

5859
private static class DiscoveryEncodeAddressAsHostname

0 commit comments

Comments
 (0)