Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions src/main/java/com/tencentcloudapi/common/AbstractClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,12 @@ public void setCredential(Credential credential) {
* @throws TencentCloudSDKException If an error occurs during the API call.
*/
public String call(String action, String jsonPayload) throws TencentCloudSDKException {
HashMap<String, String> headers = this.getHeaders();
Credential credSnapshot = this.credential.getSnapshot();
HashMap<String, String> headers = this.getHeaders(credSnapshot);
headers.put("X-TC-Action", action);
headers.put("Content-Type", "application/json; charset=utf-8");
byte[] requestPayload = jsonPayload.getBytes(StandardCharsets.UTF_8);
String authorization = this.getAuthorization(headers, requestPayload);
String authorization = this.getAuthorization(headers, requestPayload, credSnapshot);
headers.put("Authorization", authorization);
String url = this.profile.getHttpProfile().getProtocol() + this.getEndpoint() + this.path;
return this.getResponseBody(url, headers, requestPayload);
Expand All @@ -228,10 +229,11 @@ public String call(String action, String jsonPayload) throws TencentCloudSDKExce
*/
public String callOctetStream(String action, HashMap<String, String> headers, byte[] body)
throws TencentCloudSDKException {
headers.putAll(this.getHeaders());
Credential credSnapshot = this.credential.getSnapshot();
headers.putAll(this.getHeaders(credSnapshot));
headers.put("X-TC-Action", action);
headers.put("Content-Type", "application/octet-stream; charset=utf-8");
String authorization = this.getAuthorization(headers, body);
String authorization = this.getAuthorization(headers, body, credSnapshot);
headers.put("Authorization", authorization);
String url = this.profile.getHttpProfile().getProtocol() + this.getEndpoint() + this.path;
return this.getResponseBody(url, headers, body);
Expand All @@ -242,15 +244,15 @@ public String callOctetStream(String action, HashMap<String, String> headers, by
*
* @return A HashMap containing the headers.
*/
private HashMap<String, String> getHeaders() {
private HashMap<String, String> getHeaders(Credential credSnapshot) {
HashMap<String, String> headers = new HashMap<String, String>();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
headers.put("X-TC-Timestamp", timestamp);
headers.put("X-TC-Version", this.apiVersion);
headers.put("X-TC-Region", this.getRegion());
headers.put("X-TC-RequestClient", SDK_VERSION);
headers.put("Host", this.getEndpoint());
String token = this.credential.getToken();
String token = credSnapshot.getToken();
if (token != null && !token.isEmpty()) {
headers.put("X-TC-Token", token);
}
Expand All @@ -271,7 +273,7 @@ private HashMap<String, String> getHeaders() {
* @return The authorization header string.
* @throws TencentCloudSDKException If an error occurs during signature generation.
*/
private String getAuthorization(HashMap<String, String> headers, byte[] body)
private String getAuthorization(HashMap<String, String> headers, byte[] body, Credential credSnapshot)
throws TencentCloudSDKException {
String endpoint = this.getEndpoint();
// always use post tc3-hmac-sha256 signature process
Expand Down Expand Up @@ -314,8 +316,8 @@ private String getAuthorization(HashMap<String, String> headers, byte[] body)
String stringToSign =
"TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;

String secretId = this.credential.getSecretId();
String secretKey = this.credential.getSecretKey();
String secretId = credSnapshot.getSecretId();
String secretKey = credSnapshot.getSecretKey();
byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
byte[] secretService = Sign.hmac256(secretDate, service);
byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
Expand Down Expand Up @@ -742,6 +744,7 @@ private Response doRequest(String endpoint, AbstractModel request, String action
*/
private Response doRequestWithTC3(String endpoint, AbstractModel request, String action)
throws TencentCloudSDKException, IOException {
Credential credSnapshot = this.credential.getSnapshot();
String httpRequestMethod = this.profile.getHttpProfile().getReqMethod();
if (httpRequestMethod == null) {
throw new TencentCloudSDKException(
Expand Down Expand Up @@ -809,8 +812,8 @@ private Response doRequestWithTC3(String endpoint, AbstractModel request, String
if (skipSign) {
authorization = "SKIP";
} else {
String secretId = this.credential.getSecretId();
String secretKey = this.credential.getSecretKey();
String secretId = credSnapshot.getSecretId();
String secretKey = credSnapshot.getSecretKey();
byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
byte[] secretService = Sign.hmac256(secretDate, service);
byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
Expand Down Expand Up @@ -845,7 +848,7 @@ private Response doRequestWithTC3(String endpoint, AbstractModel request, String
if (null != this.getRegion()) {
hb.add("X-TC-Region", this.getRegion());
}
String token = this.credential.getToken();
String token = credSnapshot.getToken();
if (token != null && !token.isEmpty()) {
hb.add("X-TC-Token", token);
}
Expand Down Expand Up @@ -956,15 +959,19 @@ private String getCanonicalQueryString(HashMap<String, String> params, String me
*/
private String formatRequestData(String action, Map<String, String> param)
throws TencentCloudSDKException {
Credential credSnapshot = this.credential.getSnapshot();
String secretId = credSnapshot.getSecretId();
String secretKey = credSnapshot.getSecretKey();
String token = credSnapshot.getToken();
param.put("Action", action);
param.put("RequestClient", this.sdkVersion);
param.put("Nonce", String.valueOf(Math.abs(new SecureRandom().nextInt())));
param.put("Timestamp", String.valueOf(System.currentTimeMillis() / 1000));
param.put("Version", this.apiVersion);

// Add SecretId, Region, SignatureMethod, and Token if available.
if (this.credential.getSecretId() != null && (!this.credential.getSecretId().isEmpty())) {
param.put("SecretId", this.credential.getSecretId());
if (secretId != null && (!secretId.isEmpty())) {
param.put("SecretId", secretId);
}

if (this.region != null && (!this.region.isEmpty())) {
Expand All @@ -975,8 +982,8 @@ private String formatRequestData(String action, Map<String, String> param)
param.put("SignatureMethod", this.profile.getSignMethod());
}

if (this.credential.getToken() != null && (!this.credential.getToken().isEmpty())) {
param.put("Token", this.credential.getToken());
if (token != null && (!token.isEmpty())) {
param.put("Token", token);
}

if (null != this.profile.getLanguage()) {
Expand All @@ -994,7 +1001,7 @@ private String formatRequestData(String action, Map<String, String> param)
this.path);
// Generate the signature.
String sigOutParam =
Sign.sign(this.credential.getSecretKey(), sigInParam, this.profile.getSignMethod());
Sign.sign(secretKey, sigInParam, this.profile.getSignMethod());

String strParam = "";
try {
Expand Down
85 changes: 85 additions & 0 deletions src/main/java/com/tencentcloudapi/common/Credential.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
* <p>Ephemeral credential, can be obtained from Security Token Service (STS), has three dimensions:
* SecretId, SecretKey and Token. It will expire after a short time, hence you need to invoke STS
* API to refresh it.
*
* <p><b>Thread-safety and atomicity.</b> The triple (secretId, secretKey, token) is only guaranteed
* to be read atomically via {@link #getSnapshot()}. The individual {@code getSecretId()},
* {@code getSecretKey()}, {@code getToken()} getters do NOT guarantee that two consecutive calls
* observe a consistent triple — a refresh triggered between them may yield an id/key/token
* mismatch. Any code path that consumes more than one of the three fields together (e.g. request
* signing) should use {@link #getSnapshot()}.
*/
public class Credential {
private String secretId;
Expand Down Expand Up @@ -65,6 +72,23 @@ public void setUpdater(Updater updater) {
this.updater = updater;
}

/**
* Returns the secret id, triggering a refresh via the attached {@link Updater} if one is set.
*
* <p><b>Backward-compatibility note.</b> This getter calls {@link #tryUpdate()} outside of any
* synchronization block. Two consequences follow from this:
* <ol>
* <li>Concurrent threads can each enter {@code tryUpdate()} simultaneously, so the
* {@link Updater} may be invoked concurrently; well-behaved Updater implementations must
* guard against this themselves (e.g. via {@code needRefresh()} checks).</li>
* <li>An {@link Updater} that calls back into {@code getSecretId()}, {@code getSecretKey()},
* or {@code getToken()} on the same credential will recurse infinitely. Use
* {@link #setCredential} for writes inside {@code update()}, never the individual
* getters.</li>
* </ol>
* Both behaviours are retained for backward compatibility. For any code path that reads more
* than one of the three fields together, use {@link #getSnapshot()} instead.
*/
public String getSecretId() {
tryUpdate();
return this.secretId;
Expand All @@ -74,6 +98,12 @@ public void setSecretId(String secretId) {
this.secretId = secretId;
}

/**
* Returns the secret key, triggering a refresh via the attached {@link Updater} if one is set.
*
* <p>See {@link #getSecretId()} for the backward-compatibility caveats that apply to all three
* individual getters. Prefer {@link #getSnapshot()} when reading multiple fields together.
*/
public String getSecretKey() {
tryUpdate();
return this.secretKey;
Expand All @@ -83,6 +113,13 @@ public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

/**
* Returns the session token, triggering a refresh via the attached {@link Updater} if one is
* set.
*
* <p>See {@link #getSecretId()} for the backward-compatibility caveats that apply to all three
* individual getters. Prefer {@link #getSnapshot()} when reading multiple fields together.
*/
public String getToken() {
tryUpdate();
return this.token;
Expand All @@ -92,6 +129,54 @@ public void setToken(String token) {
this.token = token;
}

/**
* Atomically replaces the entire (secretId, secretKey, token) triple under the same monitor
* used by {@link #getSnapshot()}.
*
* <p>Use this instead of three separate {@code setSecretId} / {@code setSecretKey} /
* {@code setToken} calls whenever you need to publish a freshly-refreshed triple: concurrent
* readers going through {@link #getSnapshot()} are guaranteed to observe either the old triple
* or the new triple in its entirety, never a mix of the two. The individual setters do not
* provide this guarantee and should only be used when no other thread is reading.
*
* <p>This method is the intended write-side companion to {@link #getSnapshot()}'s read-side
* atomicity. {@link Updater} implementations that refresh the triple in place (rather than
* constructing a new {@code Credential}) should call this method instead of the individual
* setters.
*
* @param secretId the new secret id.
* @param secretKey the new secret key.
* @param token the new token, may be empty or {@code null} for permanent credentials.
*/
public void setCredential(String secretId, String secretKey, String token) {
synchronized (this) {
this.secretId = secretId;
this.secretKey = secretKey;
this.token = token;
}
}

/**
* Returns a point-in-time, self-consistent copy of the (secretId, secretKey, token) triple.
*
* <p>This is the only thread-safe, atomic way to read the credential triple. The refresh hook
* (if any) is invoked exactly once under a lock, and the three fields are then sampled together
* into a new {@code Credential} that does not carry an {@link Updater}. Use this in any code
* path that consumes more than one of the three fields together (e.g. request signing).
*
* <p>The returned object should be treated as read-only. It is a fresh instance and mutating it
* via the setters has no effect on the source credential, but doing so will break the
* consistency guarantee for the holder of the snapshot.
*
* @return a point-in-time copy of the credential triple, with no attached updater.
*/
public Credential getSnapshot() {
synchronized (this) {
tryUpdate();
return new Credential(secretId, secretKey, token);
}
}

private void tryUpdate() {
if (updater == null) {
return;
Expand Down
Loading