3030
3131import javax .crypto .SecretKey ;
3232
33+ import java .time .Instant ;
3334import java .time .ZonedDateTime ;
3435import java .util .Date ;
36+ import java .util .concurrent .atomic .AtomicReference ;
37+ import java .util .function .Function ;
38+ import java .util .function .Supplier ;
3539
3640import static io .airlift .http .client .Request .Builder .fromRequest ;
3741import static io .jsonwebtoken .security .Keys .hmacShaKeyFor ;
4145import static jakarta .ws .rs .core .MediaType .TEXT_PLAIN_TYPE ;
4246import static jakarta .ws .rs .core .Response .Status .UNAUTHORIZED ;
4347import static java .nio .charset .StandardCharsets .UTF_8 ;
48+ import static java .time .temporal .ChronoUnit .MINUTES ;
4449import static java .util .Objects .requireNonNull ;
4550
4651public 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}
0 commit comments