Estos contenidos se han traducido de forma automática para su comodidad, pero Huawei Cloud no garantiza la exactitud de estos. Para consultar los contenidos originales, acceda a la versión en inglés.
Actualización más reciente 2024-09-18 GMT+08:00

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.

El StringToSign se construye de acuerdo con las siguientes reglas. Tabla 1 describe los parámetros.
StringToSign = 
    HTTP-Verb + "\n" + 
    Content-MD5 + "\n" + 
    Content-Type + "\n" + 
    Date + "\n" + 
    CanonicalizedHeaders + CanonicalizedResource
Tabla 1 Parámetros requeridos para construir un StringToSign

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.

  1. Todos los caracteres de las palabras clave en un campo de encabezado de solicitud deben convertirse en letras minúsculas (los valores de contenido deben distinguir entre mayúsculas y minúsculas, por ejemplo, x-obs-storage-class:STANDARD). Si una solicitud contiene varios campos de encabezado, estos campos deben organizarse por palabra clave en el orden alfabético desde a hasta z.
  2. Si varios campos de encabezado de una solicitud tienen el mismo prefijo, combine los campos de encabezado en uno. Por ejemplo, x-obs-meta-name:name1 y x-obs-meta-name:name2 deben reorganizarse en x-obs-meta-name:name1,name2. Utilice la coma para separar los valores.
  3. Las palabras clave en el campo de encabezado de solicitud no pueden contener caracteres no ASCII o irreconocibles, que tampoco son recomendables para los valores en el campo de encabezado de solicitud. Si los dos tipos de caracteres son necesarios, deben codificarse y decodificarse en el lado del cliente. La codificación URL o la codificación Base64 son aceptables, pero el servidor no realiza la decodificación.
  4. Eliminar espacios y tabulaciones sin sentido en un campo de encabezado. Por ejemplo, x-obs-meta-name: name (con un espacio sin sentido antes de name) debe cambiarse a x-obs-meta-name:name.
  5. Cada campo de encabezado ocupa una línea separada. Vea Tabla 4.

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] +...

  1. Nombre del bucket y nombre del objeto
    • Si el bucket está enlazado a un nombre de dominio de usuario, utilice el nombre de dominio de usuario como nombre de bucket, por ejemplo, /obs.ccc.com/object. obs.ccc.com es el nombre de dominio del usuario vinculado al bucket. Si no se especifica ningún nombre de objeto, se muestra el bucket completo, por ejemplo, /obs.ccc.com/.
    • Si no accede a OBS con un nombre de dominio de usuario, este campo tiene el formato de /bucket/object. Si no se especifica ningún nombre de objeto, por ejemplo, /bucket/, se muestra el bucket completo. Si no se especifica el nombre del bucket, el valor de este campo es /.
  2. Si existe un subrecurso (como ?acl y ?logging), se debe agregar el subrecurso.

    OBS soporta una variedad de sub-recursos, incluidos CDNNotifyConfiguration, acl, append, attname, backtosource, cors, customdomain, delete, deletebucket, directcoldaccess, encryption, inventory, length, lifecycle, location, logging, metadata, modify, name, notification, 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, object-lock, retention, y x-obs-security-token.

  3. Si hay varios subrecursos, ordenarlos en el orden alfabético de a a z y utilizar & para combinar los subrecursos.
NOTA:
  • Un subrecurso es único. No agregue subrecursos con la misma palabra clave (por ejemplo, key=value1&key=value2) en la misma URL de solicitud. En este caso, la firma se calcula solo en base al primer subrecurso, y solo el valor del primer subrecurso tiene efecto en el servicio real.
  • Usando la API GetObject como ejemplo, suponga que hay un bucket llamado bucket-test y un objeto llamado object-test en el bucket, y la versión del objeto es xxx. Al obtener el objeto, es necesario reescribir Content-Type a text/plain. Entonces, el CanonicalizedResource calculado por la firma es /bucket-test/object-test?response-content-type=text/plain&versionId=xxx.

Las siguientes tablas proporcionan algunos ejemplos de generación de StringToSign.

Tabla 2 Obtención de un objeto

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

Tabla 3 Uso de AK/SK temporal y token de seguridad para cargar objetos

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

Tabla 4 Una solicitud de carga de objetos que contiene campos de encabezado

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

Tabla 5 Obtención de una ACL de objeto

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

Tabla 6 Una solicitud de carga de objeto que lleva el encabezado Content-MD5

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

Tabla 7 Subir un objeto a través de un nombre de dominio de usuario

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();
          }
     }
}
La firma se genera como sigue basándose en el StringToSign y la SK. El algoritmo de código de autenticación de mensajes basado en hash (HMAC algorithm) se usa para generar la firma.
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )

Por ejemplo, para crear un bucket privado denominado newbucketname2 en una región, el formato de solicitud del cliente es el siguiente:
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.

  1. La API para calcular la firma está contenida en el archivo de encabezado sign.h.
  2. 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?