データセキュリティを確保するために、すべての API リクエストに署名する必要があります。Alibaba Cloud は、リクエスト署名を使用して API 呼び出し元の ID を検証します。したがって、リクエストが HTTP 経由で送信されるか HTTPS 経由で送信されるかに関係なく、各 API リクエストには署名情報が含まれている必要があります。
リクエストに署名する
API リクエストに署名するには、[ユーザー管理コンソール] にログインして AccessKey ペアを取得し、メッセージ認証コード(MAC)を計算する必要があります。AccessKey ペアは、AccessKey ID と AccessKey シークレットで構成されます。AccessKey ID は API 呼び出し元の ID を検証するために使用され、AccessKey シークレットは署名文字列の暗号化と検証に使用されます。 AccessKey シークレットは極秘にしておく必要があります。
IoT Platform は、Java、Python、PHP など、複数のプログラミング言語用の SDK を提供しています。SDK を使用して API オペレーションを呼び出す場合、署名プロセスをスキップできます。SDK の使用方法の詳細については、「IoT Platform SDK(オリジナル)」および「IoT Platform SDK(アップグレード版)」をご参照ください。
リクエストに署名するには、次の手順を実行します。
リクエストパラメーターを配置して、正規化されたクエリ文字列を作成します。
リクエストパラメーターをソートします。
すべてのリクエストパラメーターをアルファベット順にソートします。リクエストパラメーターには、Signature パラメーターを除くすべての 共通パラメーターと操作固有のパラメーターが含まれます。
説明GET メソッドを使用してリクエストを送信する場合、リクエスト URL 内の疑問符(
?)の後にあるパラメーターで、アンパサンド(&)で接続されているものがリクエストパラメーターです。正規化されたクエリ文字列をエンコードします。
RFC 3986 に基づいて、UTF-8 を使用してリクエストパラメーターの名前と値をエンコードします。エンコード形式は、次のルールに準拠している必要があります。
文字、数字、ハイフン(
-)、アンダースコア(_)、ピリオド(.)、およびチルダ(~)はエンコードする必要はありません。その他の文字は、
%XY形式でパーセントエンコードする必要があります。XYは、16 進表記の文字の ASCII コードを表します。たとえば、二重引用符(")は%22としてエンコードされます。拡張 UTF-8 文字は、
%XY%ZA…形式でエンコードされます。スペースは
%20としてエンコードする必要があります。スペースをプラス記号(+)としてエンコードしないでください。
上記のエンコード方法は、
application/x-www-form-urlencodedMIME(Multipurpose Internet Mail Extensions)エンコードアルゴリズムと似ていますが、わずかに異なります。Java 標準ライブラリの
java.net.URLEncoderを使用する場合は、percentEncodeを使用してリクエストパラメーターとその値をエンコードします。エンコードされたクエリ文字列では、プラス記号(+)を%20に、アスタリスク(*)を%2Aに、%7Eをチルダ(~)に置き換えます。このようにして、上記のエンコードルールに一致するエンコードされたクエリ文字列を取得できます。private static final String ENCODING = "UTF-8"; // エンコーディングを UTF-8 に設定 private static String percentEncode(String value) throws UnsupportedEncodingException { // パーセントエンコードを行うメソッド return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null; // 値が null でない場合、エンコードを行い、特定の文字を置換する }エンコードされたリクエストパラメーター名と値を等号(=)でつなぎます。
エンコードされたリクエストパラメーターをパラメーター名でアルファベット順にソートし、アンパサンド(
&)を使用してパラメーターを接続します。
このようにして、正規化されたクエリ文字列(CanonicalizedQueryString)が作成されます。
署名対象の文字列を作成します。
percentEncodeメソッドを使用して、エンコードされた正規化クエリ文字列から署名対象の文字列を作成します。次のサンプルコードは、署名対象の文字列を作成する方法を示しています。StringToSign= HTTPMethod + "&" + // HTTP メソッド percentEncode("/") + "&" + // "/" をエンコード percentEncode(CanonicalizedQueryString) // 正規化されたクエリ文字列をエンコードパラメーター:
HTTPMethod:リクエストの送信に使用される HTTP メソッド(例:GET)。
percentEncode(“/“):UTF-8 を使用してバックスラッシュ(/)を %2F としてエンコードします。
percentEncode(CanonicalizedQueryString) // 前の手順で作成した正規化されたクエリ文字列をエンコードします。
署名対象文字列のハッシュベースのメッセージ認証コード(HMAC)を計算します。
string-to-signの HMAC を RFC 2104 に基づいて計算します。HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) // AccessSecret をキーとして、StringToSign の HMAC-SHA1 を計算重要セキュアハッシュアルゴリズム 1 (SHA-1) アルゴリズムを使用して、署名対象文字列の HMAC を計算します。 AccessKey シークレットにアンパサンド (
&) (ASCII:38) を追加したものが、HMAC 計算のキーとして使用されます。署名文字列を計算します。
HMAC を Base64 でエンコードして、署名文字列を取得します。
Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) ) // HMAC-SHA1 の結果を Base64 エンコードして署名文字列を生成署名文字列を追加します。
署名文字列を Signature パラメーターとしてリクエストに追加し、RFC 3986 に基づいて URL エンコードを実行します。
例
次の例は、リクエストに署名する方法を示しています。この例では、IoT Platform の Pub オペレーションが呼び出されます。リクエスト URL には、次のリクエストパラメーターが構成されています。AccessKeyId=testid、AccessKeySecret=testsecret、ProductKey=12345abcde、TopicFullName=/12345abcde/testdevice/user/get、MessageContent=aGVsbG8gd29ybGQ、および Qos=0。
正規化されたクエリ文字列を作成してエンコードします。
http://iot.cn-shanghai.aliyuncs.com/?Action=Pub&MessageContent=aGVsbG8gd29ybGQ&Timestamp=2018-07-31T07:43:57Z&SignatureVersion=1.0&Format=XML&Qos=0&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2018-01-20&AccessKeyId=testid&SignatureMethod=HMAC-SHA1&RegionId=cn-shanghai&ProductKey=12345abcde&TopicFullName=/12345abcde/testdevice/user/getエンコードされた正規化クエリ文字列から string-to-sign を作成します。
GET&%2F&AccessKeyId%3Dtestid%26Action%3DPub%26Format%3DXML%26MessageContent%3DaGVsbG8gd29ybGQ%26ProductKey%3D12345abcde%26Qos%3D0%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf%26SignatureVersion%3D1.0%26Timestamp%3D2018-07-31T07%253A43%253A57Z%26TopicFullName%3D%252F12345abcde%252Ftestdevice%252Fuser%252Fget%26Version%3D2018-01-20署名文字列を計算します。
この例では、AccessKey シークレットは
testsecretです。HMAC 計算に使用されるキーは testsecret& です。次のコードは、計算された署名文字列を示しています。NUh3otvAoXOZmG/a2gDShh6Ze9w=署名文字列を Signature パラメーターとしてリクエストに追加します。次のコードは、署名付きリクエストの URL を示しています。
http://iot.cn-shanghai.aliyuncs.com/?MessageContent=aGVsbG8gd29ybGQ&Action=Pub&Timestamp=2018-07-31T07%253A43%253A57Z&SignatureVersion=1.0&Format=XML&Qos=0&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2018-01-20&AccessKeyId=testid&Signature=NUh3otvAoXOZmG%2Fa2gDShh6Ze9w%3D&SignatureMethod=HMAC-SHA1&RegionId=cn-shanghai&ProductKey=12345abcde&TopicFullName=%2F12345abcde%2Ftestdevice%2Fuser%2Fget
Java のサンプルコード
次の例は、Java を使用して署名を取得する方法を示しています。
Maven プロジェクトに次の依存関係を追加します。
<!-- Apache Commons Lang 3.x --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> <!-- 最新バージョンを使用 --> </dependency> <!-- Apache Commons Codec --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> <!-- 最新バージョンを使用 --> </dependency>Config.java ファイルを変更します。
/* * Copyright © 2018 Alibaba. All rights reserved. */ package com.aliyun.iot.demo.sign; /** * サーバー API の署名構成。 * * @author: ali * @version: 0.1 2018-08-08 08:23:54 */ public class Config { // AccessKey ペア。 public static String accessKey = "123456******3456"; public static String accessKeySecret = "123456******34567******4567890"; public final static String CHARSET_UTF8 = "utf8"; }パラメーター
例
説明
accessKey
123456******3456
IoT Platform コンソールにログインし、プロファイル画像にポインターを移動して、[accesskey 管理] をクリックして、AccessKey ID と AccessKey シークレットを取得します。
説明RAM ユーザーを使用する場合は、AliyunIOTFullAccess 権限ポリシーをユーザーにアタッチする必要があります。このポリシーにより、ユーザーは IoT Platform リソースを管理できます。そうでない場合、IoT Platform との接続は失敗します。RAM ユーザーに権限を付与する方法の詳細については、「RAM ユーザーとして IoT Platform にアクセスする」をご参照ください。
accessKeySecret
123456******34567******4567890
UrlUtil.java ファイルを変更します。
/* * Copyright © 2018 Alibaba. All rights reserved. */ package com.aliyun.iot.demo.sign; import java.net.URLEncoder; import java.util.Map; import org.apache.commons.lang3.StringUtils; /** * URL 処理クラス。 * * @author: ali * @version: 0.1 2018-06-21 20:40:52 */ public class UrlUtil { private final static String CHARSET_UTF8 = "utf8"; public static String urlEncode(String url) { // URL をエンコードするメソッド if (!StringUtils.isEmpty(url)) { // URL が空でない場合 try { url = URLEncoder.encode(url, "UTF-8"); // UTF-8 でエンコード } catch (Exception e) { System.out.println("Url encode error:" + e.getMessage()); // エラーが発生した場合、エラーメッセージを出力 } } return url; // エンコードされた URL を返す } public static String generateQueryString(Map<String, String> params, boolean isEncodeKV) { // クエリ文字列を生成するメソッド StringBuilder canonicalizedQueryString = new StringBuilder(); // StringBuilder を使用してクエリ文字列を構築 for (Map.Entry<String, String> entry : params.entrySet()) { // パラメーターを反復処理 if (isEncodeKV) // キーと値をエンコードする必要がある場合 canonicalizedQueryString.append(percentEncode(entry.getKey())).append("=") // キーをパーセントエンコード .append(percentEncode(entry.getValue())).append("&"); // 値をパーセントエンコード else // エンコードが不要な場合 canonicalizedQueryString.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); // キーと値をそのまま追加 } if (canonicalizedQueryString.length() > 1) { // クエリ文字列が空でない場合 canonicalizedQueryString.setLength(canonicalizedQueryString.length() - 1); // 最後の "&" を削除 } return canonicalizedQueryString.toString(); // 生成されたクエリ文字列を返す } public static String percentEncode(String value) { // パーセントエンコードを行うメソッド try { // URLEncoder.encode を使用してエンコードした後、プラス記号(+)、アスタリスク(*)、および %7E を API のエンコード標準に準拠する値に置き換えます。 return value == null ? null : URLEncoder.encode(value, CHARSET_UTF8).replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); // 値が null でない場合、エンコードを行い、特定の文字を置換する } catch (Exception e) { // エラーが発生した場合、何もしない } return ""; // 空文字列を返す } }SignatureUtils.java ファイルを変更します。
/* * Copyright © 2018 Alibaba. All rights reserved. */ package com.aliyun.iot.demo.sign; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Map; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; /** * サーバー API の署名構成。 * * @author: ali * @version: 0.1 2018-06-21 20:47:05 */ public class SignatureUtils { private final static String CHARSET_UTF8 = "utf8"; private final static String ALGORITHM = "HmacSHA1"; private final static String SEPARATOR = "&"; public static Map<String, String> splitQueryString(String url) // クエリ文字列を分割するメソッド throws URISyntaxException, UnsupportedEncodingException { URI uri = new URI(url); // URI オブジェクトを作成 String query = uri.getQuery(); // クエリ文字列を取得 final String[] pairs = query.split("&"); // "&" で分割 TreeMap<String, String> queryMap = new TreeMap<String, String>(); // TreeMap を使用してキーと値を格納 for (String pair : pairs) { // 分割されたクエリ文字列を反復処理 final int idx = pair.indexOf("="); // "=" のインデックスを取得 final String key = idx > 0 ? pair.substring(0, idx) : pair; // キーを取得 if (!queryMap.containsKey(key)) { // キーが既に存在しない場合 queryMap.put(key, URLDecoder.decode(pair.substring(idx + 1), CHARSET_UTF8)); // 値をデコードして TreeMap に追加 } } return queryMap; // 分割されたクエリ文字列を返す } public static String generate(String method, Map<String, String> parameter, String accessKeySecret) // 署名を生成するメソッド throws Exception { String signString = generateSignString(method, parameter); // 署名対象文字列を生成 System.out.println("signString---" + signString); // 署名対象文字列を出力 byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", signString); // HMAC-SHA1 署名を計算 String signature = newStringByBase64(signBytes); // 署名を Base64 エンコード System.out.println("signature----" + signature); // 署名を出力 if ("POST".equals(method)) // POST メソッドの場合 return signature; // エンコードされていない署名を返す return URLEncoder.encode(signature, "UTF-8"); // GET メソッドの場合、署名を URL エンコードして返す } public static String generateSignString(String httpMethod, Map<String, String> parameter) throws IOException { // 署名対象文字列を生成するメソッド TreeMap<String, String> sortParameter = new TreeMap<String, String>(); // TreeMap を使用してパラメーターをソート sortParameter.putAll(parameter); // パラメーターを TreeMap にコピー String canonicalizedQueryString = UrlUtil.generateQueryString(sortParameter, true); // 正規化されたクエリ文字列を生成 if (null == httpMethod) { // HTTP メソッドが null の場合 throw new RuntimeException("httpMethod can not be empty"); // 例外をスロー } StringBuilder stringToSign = new StringBuilder(); // StringBuilder を使用して署名対象文字列を構築 stringToSign.append(httpMethod).append(SEPARATOR); // HTTP メソッドを追加 stringToSign.append(percentEncode("/")).append(SEPARATOR); // "/" をエンコードして追加 stringToSign.append(percentEncode(canonicalizedQueryString)); // 正規化されたクエリ文字列をエンコードして追加 return stringToSign.toString(); // 生成された署名対象文字列を返す } public static String percentEncode(String value) { // パーセントエンコードを行うメソッド try { return value == null ? null : URLEncoder.encode(value, CHARSET_UTF8).replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); // 値が null でない場合、エンコードを行い、特定の文字を置換する } catch (Exception e) { // エラーが発生した場合、何もしない } return ""; // 空文字列を返す } public static byte[] hmacSHA1Signature(String secret, String baseString) throws Exception { // HMAC-SHA1 署名を計算するメソッド if (StringUtils.isEmpty(secret)) { // シークレットが空の場合 throw new IOException("secret can not be empty"); // 例外をスロー } if (StringUtils.isEmpty(baseString)) { // baseString が空の場合 return null; // null を返す } Mac mac = Mac.getInstance("HmacSHA1"); // HmacSHA1 アルゴリズムを使用する Mac オブジェクトを作成 SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(CHARSET_UTF8), ALGORITHM); // シークレットキーを作成 mac.init(keySpec); // Mac オブジェクトを初期化 return mac.doFinal(baseString.getBytes(CHARSET_UTF8)); // HMAC-SHA1 署名を計算して返す } public static String newStringByBase64(byte[] bytes) throws UnsupportedEncodingException { // バイト配列を Base64 エンコードするメソッド if (bytes == null || bytes.length == 0) { // バイト配列が null または空の場合 return null; // null を返す } return new String(Base64.encodeBase64(bytes, false), CHARSET_UTF8); // Base64 エンコードを行い、文字列に変換して返す } }Main.java ファイルを変更します。
/* * Copyright © 2018 Alibaba. All rights reserved. */ package com.aliyun.iot.demo.sign; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * 署名ツールのメインエントリ。 * * @author: ali * @version: 0.1 2018-09-18 15:06:48 */ public class Main { // 1. Config.java ファイルの AccessKey ペアを変更します。 // 2. すべてのパラメーターを指定する必要があります。以下の説明の方法 2 を使用することをお勧めします。 // 3. 「最終署名」の値は、取得したい署名です。 public static void main(String[] args) throws UnsupportedEncodingException { // 方法 1 System.out.println("方法 1:"); String str = "GET&%2F&AccessKeyId%3D" + Config.accessKey + "%26Action%3DRegisterDevice%26DeviceName%3D1533023037%26Format%3DJSON%26ProductKey%3DaxxxUtgaRLB%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D1533023037%26SignatureVersion%3D1.0%26Timestamp%3D2018-07-31T07%253A43%253A57Z%26Version%3D2018-01-20"; byte[] signBytes; try { signBytes = SignatureUtils.hmacSHA1Signature(Config.accessKeySecret + "&", str.toString()); // HMAC-SHA1 署名を計算 String signature = SignatureUtils.newStringByBase64(signBytes); // Base64 エンコード System.out.println("signString---" + str); // 署名対象文字列を出力 System.out.println("signature----" + signature); // 署名を出力 System.out.println("最終署名:" + URLEncoder.encode(signature, Config.CHARSET_UTF8)); // URL エンコードされた署名を出力 } catch (Exception e) { e.printStackTrace(); // エラーが発生した場合、スタックトレースを出力 } System.out.println(); // 方法 2 System.out.println("方法 2:"); Map<String, String> map = new HashMap<String, String>(); // 共通パラメーター。 map.put("Format", "JSON"); map.put("Version", "2018-01-20"); map.put("AccessKeyId", Config.accessKey); map.put("SignatureMethod", "HMAC-SHA1"); map.put("Timestamp", "2018-07-31T07:43:57Z"); map.put("SignatureVersion", "1.0"); map.put("SignatureNonce", "1533023037"); map.put("RegionId", "cn-shanghai"); // リクエストパラメーター。 map.put("Action", "RegisterDevice"); map.put("DeviceName", "1533023037"); map.put("ProductKey", "axxxUtgaRLB"); try { String signature = SignatureUtils.generate("GET", map, Config.accessKeySecret); // 署名を生成 System.out.println("最終署名:" + signature); // 署名を出力 } catch (Exception e) { e.printStackTrace(); // エラーが発生した場合、スタックトレースを出力 } System.out.println(); } }