我們建議您使用使用OpenAPI調試和整合服務端API文檔中的線上調試和範例程式碼擷取章節,基於阿里雲OpenAPI門戶擷取整合執行個體代碼。本文檔中,我們額外提供了少量可以直接啟動並執行範例程式碼供您參考。
範例程式碼概覽
程式設計語言 | 整合方式 | 憑據初始化方式 | 範例程式碼 |
Java | SDK | 使用RAM子使用者AK | |
使用OIDCRoleArn | |||
原生HTTPS(不推薦) | 使用RAM子使用者AK | ||
使用OIDCRoleArn |
Java+SDK+AK
程式設計語言:Java
整合方式:SDK
憑據初始化方式:使用RAM子使用者AK
依賴:
<dependency> <groupId>com.aliyun</groupId> <artifactId>cloudauth_intl20220809</artifactId> <version>***</version> // 請訪問SDK中心擷取最新版本:https://next.api.alibabacloud.com/api-tools/sdk/Cloudauth-intl?version=2022-08-09&language=java-tea&tab=primer-doc </dependency>範例程式碼
import com.aliyun.cloudauth_intl20220809.Client; import com.aliyun.cloudauth_intl20220809.models.InitializeRequest; import com.aliyun.cloudauth_intl20220809.models.InitializeResponse; import com.aliyun.cloudauth_intl20220809.models.InitializeResponseBody; import com.aliyun.teaopenapi.models.Config; public class Sample { private static Client client = null; static { try { // 構建Client client = createClient(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { try { // 1.構造請求參數(本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) InitializeRequest request = new InitializeRequest(); request.setProductCode("***"); request.setMerchantBizId("***"); request.setMerchantUserId("***"); request.setMetaInfo("***"); request.setDocType("********"); request.setAuthorize("*"); // 2.介面調用(本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) InitializeResponse response = client.initialize(request); // 3.擷取調用結果 Integer statusCode = response.getStatusCode();// 調用狀態代碼 String code = response.getBody().getCode();// 調用code碼 InitializeResponseBody.InitializeResponseBodyResult result = response.getBody().getResult(); // 調用返回 } catch (Exception e) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 e.printStackTrace(); } } /** * 構建Client * @return * @throws Exception */ public static Client createClient() throws Exception { Config config = new Config(); // 設定帳號ak,請不要將明文AK直接寫在代碼中,建議從環境變數中擷取,否則將導致安全性漏洞 config.setAccessKeyId("<your access key id>"); config.setAccessKeySecret("<your access key secret>"); // 設定endpoint和regionId(本樣本以香港為例,使用時請替換為您實際需要的endpoint) config.setEndpoint("cloudauth-intl.cn-hongkong.aliyuncs.com"); config.setRegionId("cn-hongkong"); return new Client(config); } }
SDK+OIDCRoleArn
程式設計語言:Java
整合方式:SDK
憑據初始化方式:使用OIDCRoleArn
依賴:
<dependency> <groupId>com.aliyun</groupId> <artifactId>credentials-java</artifactId> <version>***</version> </dependency> // credentials-java的最新版本請查閱:https://central.sonatype.com/artifact/com.aliyun/credentials-java <dependency> <groupId>com.aliyun</groupId> <artifactId>cloudauth_intl20220809</artifactId> <version>***</version> // cloudauth_intl20220809 的最新版本請查閱:https://next.api.alibabacloud.com/api-tools/sdk/Cloudauth-intl?version=2022-08-09&language=java-tea&tab=primer-doc </dependency>範例程式碼
import com.aliyun.cloudauth_intl20220809.Client; import com.aliyun.cloudauth_intl20220809.models.InitializeRequest; import com.aliyun.cloudauth_intl20220809.models.InitializeResponse; import com.aliyun.cloudauth_intl20220809.models.InitializeResponseBody; import com.aliyun.teaopenapi.models.Config; public class Sample { private static Client client = null; static { try { // 構建 client client = createClient(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { try { // 1.構造請求參數(本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) InitializeRequest request = new InitializeRequest(); request.setProductCode("***"); request.setMerchantBizId("***"); request.setMerchantUserId("***"); request.setMetaInfo("***"); request.setDocType("********"); request.setAuthorize("*"); // 2.介面調用(本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) InitializeResponse response = client.initialize(request); // 3.擷取調用結果 Integer statusCode = response.getStatusCode();// 狀態代碼 String code = response.getBody().getCode();// code碼 InitializeResponseBody.InitializeResponseBodyResult result = response.getBody().getResult();// 返回詳細內容 } catch (Exception e) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 e.printStackTrace(); } } /** * 構建Client * @return * @throws Exception */ public static Client createClient() throws Exception { Config config = new Config(); // 設定憑據憑證 config.setCredential(getCredentialClient()); // 設定endpoint和regionId(本樣本以香港為例,使用時請替換為您實際需要的endpoint) config.setEndpoint("cloudauth-intl.cn-hongkong.aliyuncs.com"); config.setRegionId("cn-hongkong"); return new Client(config); } public static com.aliyun.credentials.Client getCredentialClient() throws Exception { com.aliyun.credentials.models.Config credentialConfig = new com.aliyun.credentials.models.Config(); credentialConfig.setType("oidc_role_arn"); // RAM角色名稱ARN,可以通過環境變數ALIBABA_CLOUD_ROLE_ARN設定RoleArn credentialConfig.setRoleArn("<RoleArn>"); // OIDC供應商ARN,可以通過環境變數ALIBABA_CLOUD_OIDC_PROVIDER_ARN設定OidcProviderArn credentialConfig.setOidcProviderArn("<OidcProviderArn>"); // OIDC Token檔案路徑,可以通過環境變數ALIBABA_CLOUD_OIDC_TOKEN_FILE設定OidcTokenFilePath credentialConfig.setOidcTokenFilePath("<OidcTokenFilePath>"); // 角色會話名稱,可以通過環境變數ALIBABA_CLOUD_ROLE_SESSION_NAME設定RoleSessionName credentialConfig.setRoleSessionName("<RoleSessionName>"); // 設定更小的權限原則,非必填。樣本值:{"Statement": [{"Action": ["*"],"Effect": "Allow","Resource": ["*"]}],"Version":"1"} credentialConfig.setPolicy("<Policy>"); // 設定session到期時間 credentialConfig.setRoleSessionExpiration(3600); return new com.aliyun.credentials.Client(credentialConfig); } }
原生http+AK
程式設計語言:Java
整合方式:原生HTTPS
憑據初始化方式:使用RAM子使用者AK
依賴:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.0</version> </dependency>範例程式碼
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.*; public class Sample { /** * 請求樣本 * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // 設定API名稱 (本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) String action= "Initialize"; // IDV介面版本號碼固定為2022-08-09 String version= "2022-08-09"; // 設定endpoint(本樣本以香港為例,使用時請替換為您實際需要的endpoint) String host = "cloudauth-intl.cn-hongkong.aliyuncs.com"; // 設定帳號ak,請不要將明文AK直接寫在代碼中,建議從環境變數中擷取,否則將導致安全性漏洞 String accessKey = "<your access key id>"; String accessSecret = "<your access key secret>"; // API請求參數 (本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) Map<String, String> params = new HashMap<>(); params.put("MerchantBizId", "**********"); params.put("ProductCode", "**********"); params.put("MerchantUserId", "**********"); params.put("MetaInfo", "**********"); // 1、構建服務API請求 CallRequest callRequest = buildIDVCallRequest(accessKey, accessSecret, action, version, host, params); // 2、調用IDV服務API String result = callApi(callRequest); // 3、輸出結果 System.out.println(result); } /** * 構造請求IDV的請求參數,僅樣本參考,請根據實際情況傳入。 * @return */ private static CallRequest buildIDVCallRequest(String accessKeyId, String accessKeySecret, String action, String version, String host, Map<String, String> params) { // 請求地址 String httpMethod = "POST"; String canonicalUri = "/"; CallRequest callRequest = new CallRequest(httpMethod, canonicalUri, host, action, version, accessKeyId, accessKeySecret, null); Gson gson = (new GsonBuilder()).disableHtmlEscaping().create(); callRequest.body = gson.toJson(params).getBytes(StandardCharsets.UTF_8); callRequest.headers.put("content-type", "application/json"); // 計算簽名並設定 String authorization = getAuthorization(callRequest); callRequest.headers.put("Authorization", authorization); return callRequest; } private static String callApi(CallRequest callRequest) { try { // 通過HttpClient發送請求 String url = "https://" + callRequest.host + callRequest.canonicalUri; URIBuilder uriBuilder = new URIBuilder(url); // 添加請求參數 for (Map.Entry<String, Object> entry : callRequest.queryParam.entrySet()) { uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); } HttpUriRequest httpRequest; HttpPost httpPost = new HttpPost(uriBuilder.build()); if (callRequest.body != null) { httpPost.setEntity(new ByteArrayEntity(callRequest.body, ContentType.create(callRequest.headers.get("content-type")))); } httpRequest = httpPost; // 添加http要求標頭 for (Map.Entry<String, String> entry : callRequest.headers.entrySet()) { httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); } // 發送請求 try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) { String result = EntityUtils.toString(response.getEntity(), "UTF-8"); return result; } catch (IOException e) { // 異常處理 System.out.println("Failed to send request"); e.printStackTrace(); } } catch (URISyntaxException e) { // 異常處理 System.out.println("Invalid URI syntax"); e.printStackTrace(); } return null; } /** * 該方法用於根據傳入的HTTP要求方法、正常化的URI、查詢參數等,計算並產生授權資訊。 */ private static String getAuthorization(CallRequest callRequest) { try { // 處理queryParam中參數值為List、Map類型的參數,將參數平鋪 TreeMap<String, Object> newQueryParam = new TreeMap<>(); processObject(newQueryParam, "", callRequest.queryParam); callRequest.queryParam = newQueryParam; // 步驟 1:拼接規範請求串 // 請求參數,當請求的查詢字串為空白時,使用Null 字元串作為正常化查詢字串 StringBuilder canonicalQueryString = new StringBuilder(); callRequest.queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { // 如果canonicalQueryString已經不是空的,則在查詢參數前添加"&" if (canonicalQueryString.length() > 0) { canonicalQueryString.append("&"); } canonicalQueryString.append(queryPart); }); // 計算請求體的雜湊值 String requestPayload = ""; // 請求體,當請求本文為空白時,比如GET請求,RequestPayload固定為空白字串 String hashedRequestPayload = callRequest.body != null ? sha256Hex(callRequest.body) : sha256Hex(requestPayload.getBytes(StandardCharsets.UTF_8)); callRequest.headers.put("x-acs-content-sha256", hashedRequestPayload); // 構造要求標頭,多個正常化訊息頭,按照訊息頭名稱(小寫)的字元代碼順序以升序排列後拼接在一起 StringBuilder canonicalHeaders = new StringBuilder(); // 已簽名訊息頭列表,多個要求標頭名稱(小寫)按首字母升序排列並以英文分號(;)分隔 StringBuilder signedHeadersSb = new StringBuilder(); callRequest.headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || "host".equalsIgnoreCase(entry.getKey()) || "content-type".equalsIgnoreCase(entry.getKey())).sorted(Map.Entry.comparingByKey()).forEach(entry -> { String lowerKey = entry.getKey().toLowerCase(); String value = String.valueOf(entry.getValue()).trim(); canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); signedHeadersSb.append(lowerKey).append(";"); }); String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); String canonicalRequest = callRequest.httpMethod + "\n" + callRequest.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload; // 步驟 2:拼接待簽名字串 String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8)); // 計算正常化請求的雜湊值 String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; // 步驟 3:計算簽名 String signature = DatatypeConverter.printHexBinary(hmac256(callRequest.accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign)).toLowerCase(); // 步驟 4:拼接 Authorization return "ACS3-HMAC-SHA256" + " " + "Credential=" + callRequest.accessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature; } catch (Exception e) { // 異常處理 e.printStackTrace(); throw new RuntimeException("Get authorization failed.", e); } } /** * 遞迴處理對象,將複雜物件(如Map和List)展開為平面的索引值對 * * @param map 原始的索引值對集合,將被遞迴地更新 * @param key 當前處理的鍵,隨著遞迴的深入,鍵會帶有嵌套路徑資訊 * @param value 對應於鍵的值,可以是嵌套的Map、List或其他類型 */ private static void processObject(Map<String, Object> map, String key, Object value) { // 如果值為空白,則無需進一步處理 if (value == null) { return; } if (key == null) { key = ""; } // 當值為List類型時,遍曆List中的每個元素,並遞迴處理 if (value instanceof List<?>) { List<?> list = (List<?>) value; for (int i = 0; i < list.size(); ++i) { processObject(map, key + "." + (i + 1), list.get(i)); } } else if (value instanceof Map<?, ?>) { // 當值為Map類型時,遍曆Map中的每個索引值對,並遞迴處理 Map<?, ?> subMap = (Map<?, ?>) value; for (Map.Entry<?, ?> entry : subMap.entrySet()) { processObject(map, key + "." + entry.getKey().toString(), entry.getValue()); } } else { // 對於以"."開頭的鍵,移除開頭的"."以保持鍵的連續性 if (key.startsWith(".")) { key = key.substring(1); } // 對於byte[]類型的值,將其轉換為UTF-8編碼的字串 if (value instanceof byte[]) { map.put(key, new String((byte[]) value, StandardCharsets.UTF_8)); } else { // 對於其他類型的值,直接轉換為字串 map.put(key, String.valueOf(value)); } } } /** * 使用HmacSHA256演算法產生訊息認證碼(MAC)。 * * @param secretKey 密鑰,用於產生MAC的密鑰,必須保密。 * @param str 需要進行MAC認證的訊息。 * @return 返回使用HmacSHA256演算法計算出的訊息認證碼。 * @throws Exception 如果初始化MAC或計算MAC過程中遇到錯誤,則拋出異常。 */ public static byte[] hmac256(byte[] secretKey, String str) throws Exception { // 執行個體化HmacSHA256訊息認證碼產生器 Mac mac = Mac.getInstance("HmacSHA256"); // 建立密鑰規範,用於初始化MAC產生器 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm()); // 初始化MAC產生器 mac.init(secretKeySpec); // 計算訊息認證碼並返回 return mac.doFinal(str.getBytes(StandardCharsets.UTF_8)); } /** * 使用SHA-256演算法計算字串的雜湊值並以十六進位字串形式返回。 * * @param input 需要進行SHA-256雜湊計算的位元組數組。 * @return 計算結果為小寫十六進位字串。 * @throws Exception 如果在擷取SHA-256訊息摘要執行個體時發生錯誤。 */ public static String sha256Hex(byte[] input) throws Exception { // 擷取SHA-256訊息摘要執行個體 MessageDigest md = MessageDigest.getInstance("SHA-256"); // 計算字串s的SHA-256雜湊值 byte[] d = md.digest(input); // 將雜湊值轉換為小寫十六進位字串並返回 return DatatypeConverter.printHexBinary(d).toLowerCase(); } /** * 對指定的字串進行URL編碼。 * 使用UTF-8編碼字元集對字串進行編碼,並對特定的字元進行替換,以符合URL編碼規範。 * * @param str 需要進行URL編碼的字串。 * @return 編碼後的字串。其中,加號"+"被替換為"%20",星號"*"被替換為"%2A",波浪號"%7E"被替換為"~"。 */ public static String percentCode(String str) { if (str == null) { throw new IllegalArgumentException("輸入字串不可為null"); } try { return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8編碼不被支援", e); } } private static class CallRequest { // HTTP Method private final String httpMethod; // 請求路徑,當資源路徑為空白時,使用正斜杠(/)作為CanonicalURI private final String canonicalUri; // endpoint private final String host; // API name private final String xAcsAction; // API version private final String xAcsVersion; // accessKeyId private final String accessKeyId; // accessKeySecret private final String accessKeySecret; // STS令牌,非STS鑒權方式時,該參數為空白 private final String securityToken; // headers TreeMap<String, String> headers = new TreeMap<>(); // body參數對應的位元組數組,請求參數在中繼資料中顯示"in":"body"或"in": "formData",表示參數放在body中 byte[] body; // query參數,請求參數在中繼資料中顯示"in":"query",表示參數拼接在請求URL上 TreeMap<String, Object> queryParam = new TreeMap<>(); public CallRequest(String httpMethod, String canonicalUri, String host, String xAcsAction, String xAcsVersion, String accessKeyId, String accessKeySecret, String securityToken) { this.httpMethod = httpMethod; this.canonicalUri = canonicalUri; this.host = host; this.xAcsAction = xAcsAction; this.xAcsVersion = xAcsVersion; this.accessKeyId = accessKeyId; this.accessKeySecret = accessKeySecret; this.securityToken = securityToken; initHeader(); } // init headers private void initHeader() { headers.put("host", host); headers.put("x-acs-action", xAcsAction); headers.put("x-acs-version", xAcsVersion); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); sdf.setTimeZone(new SimpleTimeZone(0, "GMT")); // 設定日期格式化時區為GMT headers.put("x-acs-date", sdf.format(new Date())); headers.put("x-acs-signature-nonce", UUID.randomUUID().toString()); if (securityToken != null) { headers.put("x-acs-security-token", securityToken); } } } }
原生http+OIDCRoleArn
程式設計語言:Java
整合方式:原生HTTPS
憑據初始化方式:使用OIDCRoleArn
依賴:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>sts20150401</artifactId> <version>1.1.7</version> </dependency>範例程式碼
import com.aliyun.sts20150401.models.AssumeRoleWithOIDCResponse; import com.aliyun.sts20150401.models.AssumeRoleWithOIDCResponseBody; import com.aliyun.tea.TeaException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.http.client.methods.*; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.apache.http.entity.ContentType; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.*; public class Sample { /** * STS用戶端,用於擷取臨時安全性權杖(STS Token) */ private static com.aliyun.sts20150401.Client stsClient = null; static { com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config(); // 設定sts服務的Endpoint(本樣本以日本東京為例,使用時請替換為您實際需要的endpoint) config.endpoint = "sts.ap-northeast-1.aliyuncs.com"; try { stsClient = new com.aliyun.sts20150401.Client(config); } catch (Exception e) { throw new RuntimeException(e); } } /** * 請求樣本 * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // 1、基於OIDC角色擷取臨時安全性權杖(STS Token),有有效期間,實際生產中可在有效期間內緩衝使用 AssumeRoleWithOIDCResponseBody.AssumeRoleWithOIDCResponseBodyCredentials credentials = getSTSToken(); // 設定API名稱 (本樣本以Initialize介面為例,使用時請替換為您實際需要的介面) String action= "Initialize"; // IDV介面版本號碼固定為2022-08-09 String version= "2022-08-09"; // 設定endpoint(本樣本以香港為例,使用時請替換為您實際需要的endpoint) String host = "cloudauth-intl.cn-hongkong.aliyuncs.com"; // API請求參數(僅樣本參考,實際開發中需要根據實際情況傳入) Map<String, String> params = new HashMap<>(); params.put("MerchantBizId", "**********"); params.put("ProductCode", "**********"); params.put("MerchantUserId", "**********"); params.put("MetaInfo", "**********"); // 2、構建服務API請求 CallRequest callRequest = buildIDVCallRequest(credentials, action, version, host, params); // 3、調用IDV服務API String result = callApi(callRequest); // 4、輸出結果 System.out.println(result); } /** * 構造請求IDV的請求參數,僅樣本參考,請根據實際情況傳入。 * @return */ private static CallRequest buildIDVCallRequest(AssumeRoleWithOIDCResponseBody.AssumeRoleWithOIDCResponseBodyCredentials credentials, String action, String version, String host, Map<String, String> params) { // 請求地址 String httpMethod = "POST"; String canonicalUri = "/"; CallRequest callRequest = new CallRequest(httpMethod, canonicalUri, host, action, version, credentials.getAccessKeyId(), credentials.getAccessKeySecret(), credentials.getSecurityToken()); Gson gson = (new GsonBuilder()).disableHtmlEscaping().create(); callRequest.body = gson.toJson(params).getBytes(StandardCharsets.UTF_8); callRequest.headers.put("content-type", "application/json"); // 計算簽名並設定 String authorization = getAuthorization(callRequest); callRequest.headers.put("Authorization", authorization); return callRequest; } /** * 擷取扮演角色的臨時身份憑證(STS token) * STS的參考文檔:https://www.alibabacloud.com/help/zh/ram/product-overview/what-is-sts * 本樣本是基於AssumeRoleWithOIDC方式,另外還可以通過AssumeRole、AssumeRoleWithOIDC等擷取臨時身份憑證,參考文檔:https://www.alibabacloud.com/help/zh/ram/developer-reference/api-sts-2015-04-01-dir-role-assuming * @return */ public static AssumeRoleWithOIDCResponseBody.AssumeRoleWithOIDCResponseBodyCredentials getSTSToken() { com.aliyun.sts20150401.models.AssumeRoleWithOIDCRequest assumeRoleWithOIDCRequest = new com.aliyun.sts20150401.models.AssumeRoleWithOIDCRequest() .setOIDCProviderArn("***") .setRoleArn("***") .setDurationSeconds(3600L) .setOIDCToken("***") .setRoleSessionName("***"); com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); try { // 複製代碼運行請自行列印 API 的傳回值 AssumeRoleWithOIDCResponse assumeRoleWithOIDCResponse = stsClient.assumeRoleWithOIDCWithOptions(assumeRoleWithOIDCRequest, runtime); return assumeRoleWithOIDCResponse.body.credentials; } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); throw new RuntimeException(error); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); throw new RuntimeException(error); } } private static String callApi(CallRequest callRequest) { try { // 通過HttpClient發送請求 String url = "https://" + callRequest.host + callRequest.canonicalUri; URIBuilder uriBuilder = new URIBuilder(url); // 添加請求參數 for (Map.Entry<String, Object> entry : callRequest.queryParam.entrySet()) { uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); } HttpUriRequest httpRequest; HttpPost httpPost = new HttpPost(uriBuilder.build()); if (callRequest.body != null) { httpPost.setEntity(new ByteArrayEntity(callRequest.body, ContentType.create(callRequest.headers.get("content-type")))); } httpRequest = httpPost; // 添加http要求標頭 for (Map.Entry<String, String> entry : callRequest.headers.entrySet()) { httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); } // 發送請求 try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) { String result = EntityUtils.toString(response.getEntity(), "UTF-8"); return result; } catch (IOException e) { // 異常處理 System.out.println("Failed to send request"); e.printStackTrace(); } } catch (URISyntaxException e) { // 異常處理 System.out.println("Invalid URI syntax"); e.printStackTrace(); } return null; } /** * 該方法用於根據傳入的HTTP要求方法、正常化的URI、查詢參數等,計算並產生授權資訊。 */ private static String getAuthorization(CallRequest callRequest) { try { // 處理queryParam中參數值為List、Map類型的參數,將參數平鋪 TreeMap<String, Object> newQueryParam = new TreeMap<>(); processObject(newQueryParam, "", callRequest.queryParam); callRequest.queryParam = newQueryParam; // 步驟 1:拼接規範請求串 // 請求參數,當請求的查詢字串為空白時,使用Null 字元串作為正常化查詢字串 StringBuilder canonicalQueryString = new StringBuilder(); callRequest.queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { // 如果canonicalQueryString已經不是空的,則在查詢參數前添加"&" if (canonicalQueryString.length() > 0) { canonicalQueryString.append("&"); } canonicalQueryString.append(queryPart); }); // 計算請求體的雜湊值 String requestPayload = ""; // 請求體,當請求本文為空白時,比如GET請求,RequestPayload固定為空白字串 String hashedRequestPayload = callRequest.body != null ? sha256Hex(callRequest.body) : sha256Hex(requestPayload.getBytes(StandardCharsets.UTF_8)); callRequest.headers.put("x-acs-content-sha256", hashedRequestPayload); // 構造要求標頭,多個正常化訊息頭,按照訊息頭名稱(小寫)的字元代碼順序以升序排列後拼接在一起 StringBuilder canonicalHeaders = new StringBuilder(); // 已簽名訊息頭列表,多個要求標頭名稱(小寫)按首字母升序排列並以英文分號(;)分隔 StringBuilder signedHeadersSb = new StringBuilder(); callRequest.headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || "host".equalsIgnoreCase(entry.getKey()) || "content-type".equalsIgnoreCase(entry.getKey())).sorted(Map.Entry.comparingByKey()).forEach(entry -> { String lowerKey = entry.getKey().toLowerCase(); String value = String.valueOf(entry.getValue()).trim(); canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); signedHeadersSb.append(lowerKey).append(";"); }); String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); String canonicalRequest = callRequest.httpMethod + "\n" + callRequest.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload; // 步驟 2:拼接待簽名字串 String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8)); // 計算正常化請求的雜湊值 String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; // 步驟 3:計算簽名 String signature = DatatypeConverter.printHexBinary(hmac256(callRequest.accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign)).toLowerCase(); // 步驟 4:拼接 Authorization return "ACS3-HMAC-SHA256" + " " + "Credential=" + callRequest.accessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature; } catch (Exception e) { // 異常處理 e.printStackTrace(); throw new RuntimeException("Get authorization failed.", e); } } /** * 遞迴處理對象,將複雜物件(如Map和List)展開為平面的索引值對 * * @param map 原始的索引值對集合,將被遞迴地更新 * @param key 當前處理的鍵,隨著遞迴的深入,鍵會帶有嵌套路徑資訊 * @param value 對應於鍵的值,可以是嵌套的Map、List或其他類型 */ private static void processObject(Map<String, Object> map, String key, Object value) { // 如果值為空白,則無需進一步處理 if (value == null) { return; } if (key == null) { key = ""; } // 當值為List類型時,遍曆List中的每個元素,並遞迴處理 if (value instanceof List<?>) { List<?> list = (List<?>) value; for (int i = 0; i < list.size(); ++i) { processObject(map, key + "." + (i + 1), list.get(i)); } } else if (value instanceof Map<?, ?>) { // 當值為Map類型時,遍曆Map中的每個索引值對,並遞迴處理 Map<?, ?> subMap = (Map<?, ?>) value; for (Map.Entry<?, ?> entry : subMap.entrySet()) { processObject(map, key + "." + entry.getKey().toString(), entry.getValue()); } } else { // 對於以"."開頭的鍵,移除開頭的"."以保持鍵的連續性 if (key.startsWith(".")) { key = key.substring(1); } // 對於byte[]類型的值,將其轉換為UTF-8編碼的字串 if (value instanceof byte[]) { map.put(key, new String((byte[]) value, StandardCharsets.UTF_8)); } else { // 對於其他類型的值,直接轉換為字串 map.put(key, String.valueOf(value)); } } } /** * 使用HmacSHA256演算法產生訊息認證碼(MAC)。 * * @param secretKey 密鑰,用於產生MAC的密鑰,必須保密。 * @param str 需要進行MAC認證的訊息。 * @return 返回使用HmacSHA256演算法計算出的訊息認證碼。 * @throws Exception 如果初始化MAC或計算MAC過程中遇到錯誤,則拋出異常。 */ public static byte[] hmac256(byte[] secretKey, String str) throws Exception { // 執行個體化HmacSHA256訊息認證碼產生器 Mac mac = Mac.getInstance("HmacSHA256"); // 建立密鑰規範,用於初始化MAC產生器 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm()); // 初始化MAC產生器 mac.init(secretKeySpec); // 計算訊息認證碼並返回 return mac.doFinal(str.getBytes(StandardCharsets.UTF_8)); } /** * 使用SHA-256演算法計算字串的雜湊值並以十六進位字串形式返回。 * * @param input 需要進行SHA-256雜湊計算的位元組數組。 * @return 計算結果為小寫十六進位字串。 * @throws Exception 如果在擷取SHA-256訊息摘要執行個體時發生錯誤。 */ public static String sha256Hex(byte[] input) throws Exception { // 擷取SHA-256訊息摘要執行個體 MessageDigest md = MessageDigest.getInstance("SHA-256"); // 計算字串s的SHA-256雜湊值 byte[] d = md.digest(input); // 將雜湊值轉換為小寫十六進位字串並返回 return DatatypeConverter.printHexBinary(d).toLowerCase(); } /** * 對指定的字串進行URL編碼。 * 使用UTF-8編碼字元集對字串進行編碼,並對特定的字元進行替換,以符合URL編碼規範。 * * @param str 需要進行URL編碼的字串。 * @return 編碼後的字串。其中,加號"+"被替換為"%20",星號"*"被替換為"%2A",波浪號"%7E"被替換為"~"。 */ public static String percentCode(String str) { if (str == null) { throw new IllegalArgumentException("輸入字串不可為null"); } try { return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8編碼不被支援", e); } } private static class CallRequest { // HTTP Method private final String httpMethod; // 請求路徑,當資源路徑為空白時,使用正斜杠(/)作為CanonicalURI private final String canonicalUri; // endpoint private final String host; // API name private final String xAcsAction; // API version private final String xAcsVersion; // accessKeyId private final String accessKeyId; // accessKeySecret private final String accessKeySecret; // STS令牌,非STS鑒權方式時,該參數為空白 private final String securityToken; // headers TreeMap<String, String> headers = new TreeMap<>(); // body參數對應的位元組數組,請求參數在中繼資料中顯示"in":"body"或"in": "formData",表示參數放在body中 byte[] body; // query參數,請求參數在中繼資料中顯示"in":"query",表示參數拼接在請求URL上 TreeMap<String, Object> queryParam = new TreeMap<>(); public CallRequest(String httpMethod, String canonicalUri, String host, String xAcsAction, String xAcsVersion, String accessKeyId, String accessKeySecret, String securityToken) { this.httpMethod = httpMethod; this.canonicalUri = canonicalUri; this.host = host; this.xAcsAction = xAcsAction; this.xAcsVersion = xAcsVersion; this.accessKeyId = accessKeyId; this.accessKeySecret = accessKeySecret; this.securityToken = securityToken; initHeader(); } // init headers private void initHeader() { headers.put("host", host); headers.put("x-acs-action", xAcsAction); headers.put("x-acs-version", xAcsVersion); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); sdf.setTimeZone(new SimpleTimeZone(0, "GMT")); // 設定日期格式化時區為GMT headers.put("x-acs-date", sdf.format(new Date())); headers.put("x-acs-signature-nonce", UUID.randomUUID().toString()); if (securityToken != null) { headers.put("x-acs-security-token", securityToken); } } } }