Autenticación de la firma en un encabezado
Para todas las operaciones de API, la autenticación de identidad más común es llevar firmas en encabezados.
En el encabezado, la firma se lleva en el campo de encabezado de autorización del mensaje de HTTP. El formato del encabezado del mensaje es el siguiente:
Authorization: OBS AccessKeyID:signature
Authorization es un identificador (que sirve como clave). El valor de Authorization consiste en el acrónimo de servicio, AK y la firma, con el acrónimo de servicio y la AK separados por un espacio y la AK y la firma separadas por dos puntos.
El proceso de cálculo de firmas es el siguiente:
1. Construya string de caracteres de petición (StringToSign).
2. Realice la codificación UTF-8 sobre el resultado obtenido del paso anterior.
3. Utilice la SK para realizar el cálculo de la firma HMAC-SHA1 en el resultado obtenido del paso 2.
4. Realice la codificación de Base64 en el resultado del paso 3 para obtener la firma.
StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedHeaders + CanonicalizedResource
Parámetro |
Descripción |
---|---|
HTTP-Verb |
Indica un método de solicitud de HTTP compatible con la API de REST de OBS. El valor puede ser un verbo HTTP como PUT, GET o DELETE. |
Content-MD5 |
Resumen de MD5 con 128 bits codificado en base64 del mensaje de acuerdo con RFC 1864. Este parámetro puede estar vacío. Para obtener más detalles, consulte Tabla 6 y los ejemplos de algoritmos que se encuentran debajo de la tabla. |
Content-Type |
Especifica el tipo de mensaje, por ejemplo, text/plain. Si una solicitud no contiene este campo de encabezado, este parámetro se considera como una string vacía. Para obtener más información, consulte Tabla 2. |
Date |
Hora en la que se inicia una solicitud. Este parámetro utiliza el formato de tiempo RFC 1123. Si la desviación entre el tiempo especificado por este parámetro y el tiempo del servidor es superior a 15 minutos, el servidor devuelve el error 403. Este parámetro es una string vacía cuando se especifica el x-obs-date. Para obtener más información, consulte Tabla 6. Si se autoriza temporalmente una operación (por ejemplo, la obtención de un contenido de objeto), este parámetro no es necesario. |
CanonicalizedHeaders |
Campo de encabezado de solicitud de OBS en un encabezado de solicitud de HTTP, que hace referencia a los campos de encabezado que comienzan con x-obs-, como x-obs-date, x-obs-acl y x-obs-meta-*. Cuando invoque a una API, elija un encabezado que sea compatible con la API según sea necesario.
|
CanonicalizedResource |
Indica el recurso de OBS especificado por una solicitud de HTTP. Este parámetro se construye de la siguiente manera: <Nombre del bucket + Nombre del objeto> + [Subrecurso 1] + [Subrecurso 2] +...
NOTA:
|
Las siguientes tablas proporcionan algunos ejemplos de generación de StringToSign.
Encabezado de la solicitud |
StringToSign |
---|---|
GET /object.txt HTTP/1.1 Host: bucket.obs.region.myhuaweicloud.com Date: Sat, 12 Oct 2015 08:12:38 GMT |
GET \n \n \n Sat, 12 Oct 2015 08:12:38 GMT\n /bucket/object.txt |
Encabezado de la solicitud |
StringToSign |
---|---|
PUT /object.txt HTTP/1.1 User-Agent: curl/7.15.5 Host: bucket.obs.region.myhuaweicloud.com x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT x-obs-security-token: YwkaRTbdY8g7q.... content-type: text/plain Content-Length: 5913339 |
PUT\n \n text/plain\n \n x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT\n x-obs-security-token:YwkaRTbdY8g7q....\n /bucket/object.txt |
Encabezado de la solicitud |
StringToSign |
---|---|
PUT /object.txt HTTP/1.1 User-Agent: curl/7.15.5 Host: bucket.obs.region.myhuaweicloud.com Date: Mon, 14 Oct 2015 12:08:34 GMT x-obs-acl: public-read content-type: text/plain Content-Length: 5913339 |
PUT\n \n text/plain\n Mon, 14 Oct 2015 12:08:34 GMT\n x-obs-acl:public-read\n /bucket/object.txt |
Encabezado de la solicitud |
StringToSign |
---|---|
GET /object.txt?acl HTTP/1.1 Host: bucket.obs.region.myhuaweicloud.com Date: Sat, 12 Oct 2015 08:12:38 GMT |
GET \n \n \n Sat, 12 Oct 2015 08:12:38 GMT\n /bucket/object.txt?acl |
Encabezado de la solicitud |
StringToSign |
---|---|
PUT /object.txt HTTP/1.1 Host: bucket.obs.region.myhuaweicloud.com x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT Content-MD5: I5pU0r4+sgO9Emgl1KMQUg== Content-Length: 5913339 |
PUT\n I5pU0r4+sgO9Emgl1KMQUg==\n \n \n x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT\n /bucket/object.txt |
Encabezado de la solicitud |
StringToSign |
---|---|
PUT /object.txt HTTP/1.1 Host: obs.ccc.com x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT Content-MD5: I5pU0r4+sgO9Emgl1KMQUg== Content-Length: 5913339 |
PUT\n I5pU0r4+sgO9Emgl1KMQUg==\n \n \n x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT\n /obs.ccc.com/object.txt |
Algoritmo Content-MD5 en Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.security.MessageDigest; import sun.misc.BASE64Encoder; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; public class Md5{ public static void main(String[] args) { try { String exampleString = "blog"; MessageDigest messageDigest = MessageDigest.getInstance("MD5"); BASE64Encoder encoder = new BASE64Encoder(); String contentMd5 = encoder.encode(messageDigest.digest(exampleString.getBytes("utf-8"))); System.out.println("Content-MD5:" + contentMd5); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { e.printStackTrace(); } } } |
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
PUT / HTTP/1.1 Host: newbucketname2.obs.region.myhuaweicloud.comContent-Length: length Date: Fri, 06 Jul 2018 03:45:51 GMT x-obs-acl:private x-obs-storage-class:STANDARD Authorization: OBS UDSIAMSTUBTEST000254:ydH8ffpcbS6YpeOMcEZfn0wE90c= <CreateBucketConfiguration xmlns="http://obs.region.myhuaweicloud.com/doc/2015-06-30/"> <Location>region</Location> </CreateBucketConfiguration>
Cálculo de firmas en Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.omg.CosNaming.IstringHelper; public class SignDemo { private static final String SIGN_SEP = "\n"; private static final String OBS_PREFIX = "x-obs-"; private static final String DEFAULT_ENCODING = "UTF-8"; private static final List<String> SUB_RESOURCES = Collections.unmodifiableList(Arrays.asList( "CDNNotifyConfiguration", "acl", "append", "attname", "backtosource", "cors", "customdomain", "delete", "deletebucket", "directcoldaccess", "encryption", "inventory", "length", "lifecycle", "location", "logging", "metadata", "mirrorBackToSource", "modify", "name", "notification", "obscompresspolicy", "partNumber", "policy", "position", "quota","rename", "replication", "response-cache-control", "response-content-disposition","response-content-encoding", "response-content-language", "response-content-type", "response-expires","restore", "storageClass", "storagePolicy", "storageinfo", "tagging", "torrent", "truncate", "uploadId", "uploads", "versionId", "versioning", "versions", "website", "x-image-process", "x-image-save-bucket", "x-image-save-object", "x-obs-security-token", "object-lock", "retention")); private String ak; private String sk; public String urlEncode(String input) throws UnsupportedEncodingException { return URLEncoder.encode(input, DEFAULT_ENCODING) .replaceAll("%7E", "~") //for browser .replaceAll("%2F", "/") .replaceAll("%20", "+"); } private String join(List<?> items, String delimiter) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < items.size(); i++) { String item = items.get(i).toString(); sb.append(item); if (i < items.size() - 1) { sb.append(delimiter); } } return sb.toString(); } private boolean isValid(String input) { return input != null && !input.equals(""); } public String hamcSha1(String input) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { SecretKeySpec signingKey = new SecretKeySpec(this.sk.getBytes(DEFAULT_ENCODING), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); return Base64.getEncoder().encodeToString(mac.doFinal(input.getBytes(DEFAULT_ENCODING))); } private String stringToSign(String httpMethod, Map<String, String[]> headers, Map<String, String> queries, String bucketName, String objectName) throws Exception{ String contentMd5 = ""; String contentType = ""; String date = ""; TreeMap<String, String> canonicalizedHeaders = new TreeMap<String, String>(); String key; List<String> temp = new ArrayList<String>(); for(Map.Entry<String, String[]> entry : headers.entrySet()) { key = entry.getKey(); if(key == null || entry.getValue() == null || entry.getValue().length == 0) { continue; } key = key.trim().toLowerCase(Locale.ENGLISH); if(key.equals("content-md5")) { contentMd5 = entry.getValue()[0]; continue; } if(key.equals("content-type")) { contentType = entry.getValue()[0]; continue; } if(key.equals("date")) { date = entry.getValue()[0]; continue; } if(key.startsWith(OBS_PREFIX)) { for(String value : entry.getValue()) { if(value != null) { temp.add(value.trim()); } } canonicalizedHeaders.put(key, this.join(temp, ",")); temp.clear(); } } if(canonicalizedHeaders.containsKey("x-obs-date")) { date = ""; } // handle method/content-md5/content-type/date StringBuilder stringToSign = new StringBuilder(); stringToSign.append(httpMethod).append(SIGN_SEP) .append(contentMd5).append(SIGN_SEP) .append(contentType).append(SIGN_SEP) .append(date).append(SIGN_SEP); // handle canonicalizedHeaders for(Map.Entry<String, String> entry : canonicalizedHeaders.entrySet()) { stringToSign.append(entry.getKey()).append(":").append(entry.getValue()).append(SIGN_SEP); } // handle CanonicalizedResource stringToSign.append("/"); if(this.isValid(bucketName)) { stringToSign.append(bucketName).append("/"); if(this.isValid(objectName)) { stringToSign.append(this.urlEncode(objectName)); } } TreeMap<String, String> canonicalizedResource = new TreeMap<String, String>(); for(Map.Entry<String, String> entry : queries.entrySet()) { key = entry.getKey(); if(key == null) { continue; } if(SUB_RESOURCES.contains(key)) { canonicalizedResource.put(key, entry.getValue()); } } if(canonicalizedResource.size() > 0) { stringToSign.append("?"); for(Map.Entry<String, String> entry : canonicalizedResource.entrySet()) { stringToSign.append(entry.getKey()); if(this.isValid(entry.getValue())) { stringToSign.append("=").append(entry.getValue()); } stringToSign.append("&"); } stringToSign.deleteCharAt(stringToSign.length()-1); } // System.out.println(String.format("StringToSign:%s%s", SIGN_SEP, stringToSign.toString())); return stringToSign.toString(); } public String headerSignature(String httpMethod, Map<String, String[]> headers, Map<String, String> queries, String bucketName, String objectName) throws Exception { //1. stringToSign String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName); //2. signature return String.format("OBS %s:%s", this.ak, this.hamcSha1(stringToSign)); } public String querySignature(String httpMethod, Map<String, String[]> headers, Map<String, String> queries, String bucketName, String objectName, long expires) throws Exception { if(headers.containsKey("x-obs-date")) { headers.put("x-obs-date", new String[] {String.valueOf(expires)}); }else { headers.put("date", new String[] {String.valueOf(expires)}); } //1. stringToSign String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName); //2. signature return this.urlEncode(this.hamcSha1(stringToSign)); } public static void main(String[] args) throws Exception { SignDemo demo = new SignDemo(); /* Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables. In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK. */ demo.ak = System.getenv("HUAWEICLOUD_SDK_AK"); demo.sk = System.getenv("HUAWEICLOUD_SDK_SK"); String bucketName = "bucket-test"; String objectName = "hello.jpg"; Map<String, String[]> headers = new HashMap<String, String[]>(); headers.put("date", new String[] {"Sat, 12 Oct 2015 08:12:38 GMT"}); headers.put("x-obs-acl", new String[] {"public-read"}); headers.put("x-obs-meta-key1", new String[] {"value1"}); headers.put("x-obs-meta-key2", new String[] {"value2", "value3"}); Map<String, String> queries = new HashMap<String, String>(); queries.put("acl", null); System.out.println(demo.headerSignature("PUT", headers, queries, bucketName, objectName)); } } |
El resultado del cálculo de la firma es ydH8ffpcbS6YpeOMcEZfn0wE90c= que varía en función del tiempo de ejecución.
Algoritmo de firma en Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import sys import hashlib import hmac import binascii from datetime import datetime IS_PYTHON2 = sys.version_info.major == 2 or sys.version < '3' # Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables. # In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK. yourSecretAccessKeyID = os.getenv('HUAWEICLOUD_SDK_SK') httpMethod = "PUT" contentType = "application/xml" # "date" is the time when the request was actually generated date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') canonicalizedHeaders = "x-obs-acl:private\n" CanonicalizedResource = "/newbucketname2" canonical_string = httpMethod + "\n" + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + CanonicalizedResource if IS_PYTHON2: hashed = hmac.new(yourSecretAccessKeyID, canonical_string, hashlib.sha1) encode_canonical = binascii.b2a_base64(hashed.digest())[:-1] else: hashed = hmac.new(yourSecretAccessKeyID.encode('UTF-8'), canonical_string.encode('UTF-8'),hashlib.sha1) encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8') print(encode_canonical) |
El resultado del cálculo de la firma es ydH8ffpcbS6YpeOMcEZfn0wE90c= que varía en función del tiempo de ejecución.
Algoritmo de firma en el lenguaje de programación C
Descargue el ejemplo de código para calcular la firma en el lenguaje de programación C.
- La API para calcular la firma está contenida en el archivo de encabezado sign.h.
- El ejemplo de código para calcular la firma está contenido en el archivo de encabezado main.c.
Manejo de errores de coincidencia de firmas
Durante una invocación a la API de OBS, si se informa del siguiente error,
Código de estado: 403 Forbidden
Código de error: SignatureDoesNotMatch
Mensaje de error: The request signature we calculated does not match the signature you provided.Check your key and signing method.
Maneje el problema según ¿Por qué no coinciden las firmas?