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
48 changes: 29 additions & 19 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 @@ -240,17 +242,18 @@ public String callOctetStream(String action, HashMap<String, String> headers, by
/**
* Generates common HTTP headers for Tencent Cloud API requests.
*
* @param credSnapshot a point-in-time credential snapshot used to read the token atomically.
* @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 @@ -266,12 +269,13 @@ private HashMap<String, String> getHeaders() {
/**
* Generates the authorization header for TC3-HMAC-SHA256 signature.
*
* @param headers HTTP headers.
* @param body Request payload.
* @param headers HTTP headers.
* @param body Request payload.
* @param credSnapshot a point-in-time credential snapshot used to read secretId/secretKey atomically.
* @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 +318,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 +746,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 +814,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 +850,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 +961,20 @@ 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 +985,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 +1004,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
21 changes: 21 additions & 0 deletions src/main/java/com/tencentcloudapi/common/Credential.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ public void setToken(String token) {
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 deprecated 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,41 @@
import java.util.HashMap;
import java.util.Map;

public class CvmRoleCredential extends Credential {
/**
* CvmRoleCredential fetches ephemeral CAM credentials from the CVM instance metadata service.
*
* <p>It implements the deprecated {@link Credential.Updater} interface and attaches itself as the
* updater of the underlying {@link Credential} so that {@link Credential#getSnapshot()} (and the
* deprecated individual getters) trigger a refresh of the (secretId, secretKey, token) triple when
* the cached credentials are about to expire. Callers should consume the triple via
* {@link Credential#getSnapshot()} for atomicity.
*/
@SuppressWarnings("deprecation")
public class CvmRoleCredential extends Credential implements Credential.Updater {
private static final String ENDPOINT = "http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/";
private static final int EXPIRED_TIME = 300;
private String roleName;
private String secretId;
private String secretKey;
private String token;
private int expiredTime;

public CvmRoleCredential() {
super();
super.setUpdater(this);
}

public CvmRoleCredential(String roleName) {
super();
this.roleName = roleName;
super.setUpdater(this);
}

@Override
public void update(Credential credential) throws TencentCloudSDKException {
// Cached credentials are still valid; no refresh needed. This guard is essential —
// without it every getter / getSnapshot() call would trigger a metadata HTTP request.
if (super.getSecretId() != null && !needRefresh()) {
return;
}
updateCredential();
}

private void updateCredential() throws TencentCloudSDKException {
Expand All @@ -41,45 +60,14 @@ private void updateCredential() throws TencentCloudSDKException {
if (!maps.get("Code").equals("Success")) {
throw new TencentCloudSDKException("CVM role token data failed");
}
secretId = (String) maps.get("TmpSecretId");
secretKey = (String) maps.get("TmpSecretKey");
token = (String) maps.get("Token");
// Write the refreshed triple into the parent class fields so that
// getSnapshot() returns a self-consistent copy.
super.setSecretId((String) maps.get("TmpSecretId"));
super.setSecretKey((String) maps.get("TmpSecretKey"));
super.setToken((String) maps.get("Token"));
expiredTime = ((Double) maps.get("ExpiredTime")).intValue();
}

public String getSecretId() {
if (secretId == null || needRefresh()) {
try {
updateCredential();
} catch (TencentCloudSDKException e) {
return null;
}
}
return secretId;
}

public String getSecretKey() {
if (secretKey == null || needRefresh()) {
try {
updateCredential();
} catch (TencentCloudSDKException e) {
return null;
}
}
return secretKey;
}

public String getToken() {
if (token == null || needRefresh()) {
try {
updateCredential();
} catch (TencentCloudSDKException e) {
return null;
}
}
return token;
}

private boolean needRefresh() {
if (expiredTime - new Date().getTime() / 1000 <= EXPIRED_TIME) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ public class STSCredential extends Credential {
private String secretKey;
private String roleArn;
private String roleSessionName;
private String tmpSecretId;
private String tmpSecretKey;
private String token;
private String endpoint;
private int expiredTime;

Expand All @@ -35,39 +32,60 @@ public STSCredential(String secretId, String secretKey, String roleArn, String r
this(secretId, secretKey, roleArn, roleSessionName, "sts.tencentcloudapi.com");
}

/**
* @deprecated use {@link #getSnapshot()} for any multi-field read. This getter is retained for
* backward compatibility and triggers a refresh on stale state, but the read is not atomic
* across the three fields.
*/
@Override
@Deprecated
@SuppressWarnings("deprecation")
public String getSecretId() {
if (tmpSecretId == null || needRefresh()) {
if (super.getSecretId() == null || needRefresh()) {
try {
updateCredential();
} catch (TencentCloudSDKException e) {
return null;
}
}
return tmpSecretId;
return super.getSecretId();
}

/**
* @deprecated use {@link #getSnapshot()} for any multi-field read.
*/
@Override
@Deprecated
@SuppressWarnings("deprecation")
public String getSecretKey() {
if (tmpSecretKey == null || needRefresh()) {
if (super.getSecretKey() == null || needRefresh()) {
try {
updateCredential();
} catch (TencentCloudSDKException e) {
return null;
}
}
return tmpSecretKey;
return super.getSecretKey();
}

/**
* @deprecated use {@link #getSnapshot()} for any multi-field read.
*/
@Override
@Deprecated
@SuppressWarnings("deprecation")
public String getToken() {
if (token == null || needRefresh()) {
if (super.getToken() == null || needRefresh()) {
try {
updateCredential();
} catch (TencentCloudSDKException e) {
return null;
}
}
return token;
return super.getToken();
}

@SuppressWarnings("deprecation")
private void updateCredential() throws TencentCloudSDKException {
Credential cred = new Credential(secretId, secretKey);
HttpProfile httpProfile = new HttpProfile();
Expand All @@ -81,9 +99,11 @@ private void updateCredential() throws TencentCloudSDKException {
}.getType());
Map<String, Object> respmap = (Map<String, Object>) map.get("Response");
Map<String, String> credmap = (Map<String, String>) respmap.get("Credentials");
tmpSecretId = credmap.get("TmpSecretId");
tmpSecretKey = credmap.get("TmpSecretKey");
token = credmap.get("Token");
// Write the refreshed triple into the parent class fields so that
// getSnapshot() returns a self-consistent copy.
super.setSecretId(credmap.get("TmpSecretId"));
super.setSecretKey(credmap.get("TmpSecretKey"));
super.setToken(credmap.get("Token"));
expiredTime = ((Double) respmap.get("ExpiredTime")).intValue();
}

Expand Down
Loading