|
1 | 1 | package com.danubemessaging.client.internal.auth; |
2 | 2 |
|
3 | | -import com.danubemessaging.client.errors.DanubeClientException; |
4 | | -import com.danubemessaging.client.internal.connection.ConnectionManager; |
5 | 3 | import com.danubemessaging.client.internal.connection.ConnectionOptions; |
6 | | -import danube.AuthServiceGrpc; |
7 | | -import danube.DanubeApi; |
8 | 4 | import io.grpc.Metadata; |
9 | 5 | import io.grpc.stub.AbstractStub; |
10 | 6 | import io.grpc.stub.MetadataUtils; |
11 | 7 | import java.net.URI; |
12 | | -import java.time.Duration; |
13 | | -import java.time.Instant; |
14 | | -import java.util.concurrent.locks.ReentrantLock; |
15 | 8 |
|
16 | 9 | /** |
17 | | - * Handles API-key authentication and bearer token caching. |
| 10 | + * Handles JWT token insertion into gRPC request metadata. |
| 11 | + * |
| 12 | + * <p>With JWT-first authentication, the client uses a pre-generated JWT token |
| 13 | + * (from {@code danube-admin security tokens create}) that is sent as |
| 14 | + * {@code Authorization: Bearer <token>} on every gRPC request. |
18 | 15 | */ |
19 | 16 | public final class AuthService { |
20 | | - private static final Duration TOKEN_EXPIRY = Duration.ofHours(1); |
21 | 17 | private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("authorization", |
22 | 18 | Metadata.ASCII_STRING_MARSHALLER); |
23 | 19 |
|
24 | | - private final ConnectionManager connectionManager; |
25 | 20 | private final ConnectionOptions connectionOptions; |
26 | | - private final ReentrantLock tokenLock = new ReentrantLock(); |
27 | 21 |
|
28 | | - private volatile String token; |
29 | | - private volatile Instant tokenExpiry; |
30 | | - |
31 | | - public AuthService(ConnectionManager connectionManager, ConnectionOptions connectionOptions) { |
32 | | - this.connectionManager = connectionManager; |
| 22 | + public AuthService(ConnectionOptions connectionOptions) { |
33 | 23 | this.connectionOptions = connectionOptions; |
34 | 24 | } |
35 | 25 |
|
36 | | - public String authenticateClient(URI address, String apiKey) { |
37 | | - var grpcConnection = connectionManager.getConnection(address, address); |
38 | | - var client = AuthServiceGrpc.newBlockingStub(grpcConnection.grpcChannel()); |
39 | | - var request = DanubeApi.AuthRequest.newBuilder().setApiKey(apiKey).build(); |
40 | | - |
41 | | - try { |
42 | | - var response = client.authenticate(request); |
43 | | - cacheToken(response.getToken()); |
44 | | - return response.getToken(); |
45 | | - } catch (Exception e) { |
46 | | - throw new DanubeClientException("Authentication failed", e); |
47 | | - } |
48 | | - } |
49 | | - |
50 | | - public String getValidToken(URI address, String apiKey) { |
51 | | - String currentToken = token; |
52 | | - Instant expiry = tokenExpiry; |
53 | | - if (currentToken != null && expiry != null && Instant.now().isBefore(expiry)) { |
54 | | - return currentToken; |
55 | | - } |
56 | | - |
57 | | - tokenLock.lock(); |
58 | | - try { |
59 | | - currentToken = token; |
60 | | - expiry = tokenExpiry; |
61 | | - if (currentToken != null && expiry != null && Instant.now().isBefore(expiry)) { |
62 | | - return currentToken; |
63 | | - } |
64 | | - return authenticateClient(address, apiKey); |
65 | | - } finally { |
66 | | - tokenLock.unlock(); |
67 | | - } |
68 | | - } |
69 | | - |
| 26 | + /** |
| 27 | + * Inserts the Bearer token header into the given metadata if a token is |
| 28 | + * configured (static or via supplier). |
| 29 | + */ |
70 | 30 | public void insertTokenIfNeeded(Metadata metadata, URI address) { |
71 | 31 | connectionOptions |
72 | | - .apiKey() |
73 | | - .ifPresent(apiKey -> metadata.put(AUTHORIZATION, "Bearer " + getValidToken(address, apiKey))); |
| 32 | + .resolveToken() |
| 33 | + .ifPresent(token -> metadata.put(AUTHORIZATION, "Bearer " + token)); |
74 | 34 | } |
75 | 35 |
|
| 36 | + /** |
| 37 | + * Returns the stub with auth headers attached if a token is configured. |
| 38 | + */ |
76 | 39 | public <T extends AbstractStub<T>> T attachAuthIfNeeded(T stub, URI address) { |
77 | 40 | Metadata metadata = new Metadata(); |
78 | 41 | insertTokenIfNeeded(metadata, address); |
79 | 42 | return stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata)); |
80 | 43 | } |
81 | | - |
82 | | - private void cacheToken(String tokenValue) { |
83 | | - token = tokenValue; |
84 | | - tokenExpiry = Instant.now().plus(TOKEN_EXPIRY); |
85 | | - } |
86 | 44 | } |
0 commit comments