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 2023-07-11 GMT+08:00

Autenticación de la firma en un URL

OBS permite a los usuarios construir un URL para una operación específica. El URL contiene información como el AK, la firma, el período de validez y los recursos del usuario. Cualquier usuario que obtenga la URL puede realizar la operación. Después de recibir la solicitud, la OBS considera que la operación es realizada por el usuario que emite el URL. Por ejemplo, si se construye el URL de una solicitud de descarga de objeto lleva información de firma, el usuario que obtiene el URL puede descargar el objeto, pero el URL es válido solo dentro del tiempo de caducidad especificado por el parámetro de Expires. El URL que lleva la firma se utiliza para permitir que otros utilicen el URL preemitida para la autenticación de identidad cuando no se proporciona el SK, y realizar la operación predefinida.

El formato del mensaje que contiene la solicitud de firma en el URL es el siguiente:

GET /ObjectKey?AccessKeyId=AccessKeyID&Expires=ExpiresValue&Signature=signature HTTP/1.1
Host: bucketname.obs.region.myhuaweicloud.com

El formato del mensaje que contiene el AK/SK temporal y el token de seguridad en el URL para descargar objetos es el siguiente:

GET /ObjectKey?AccessKeyId=AccessKeyID&Expires=ExpiresValue&Signature=signature&x-obs-security-token=securitytoken HTTP/1.1
Host: bucketname.obs.region.myhuaweicloud.com

Tabla 1 describe los parámetros.

Tabla 1 Parámetros de solicitud

Parámetro

Descripción

Obligatorio

AccessKeyId

Información de AK del emisor. OBS determina la identidad del emisor basándose en el AK y considera que el emisor accede a el URL.

Tipo: string

Expires

Indica cuándo caduca el URL autorizada temporalmente, en segundos. La hora debe estar en formato de hora universal coordinada (UTC) y después de las 00:00:00 del 1 de enero de 1970.

Tipo: string

Signature

La firma generada usando el SK y el tiempo de caducidad.

Tipo: string

x-obs-security-token

Durante la autenticación temporal, el AK/SK temporal y el token de seguridad deben usarse al mismo tiempo y el campo x-obs-security-token debe agregarse al encabezado de solicitud.

Para obtener detalles sobre cómo obtener un par AK/SK temporal y un token de seguridad, consulte Obtención de un par AK/SK temporal y un token de seguridad.

No

El proceso de cómputo de firmas es el siguiente:

1. Construye el StringToSign.

2. Realice codificación UTF-8 sobre el resultado obtenido de la etapa anterior.

3. Utilice el 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 sobre el resultado obtenido de la etapa 3.

5. Realice la codificación de URL en el resultado del paso 4 para obtener la firma.

El StringToSign se construye de acuerdo con las siguientes reglas. Tabla 2 describe los parámetros.

StringToSign = 
     HTTP-Verb + "\n" +   
     Content-MD5 + "\n" +   
     Content-Type + "\n" +   
     Expires + "\n" +   
     CanonicalizedHeaders +   CanonicalizedResource; 
Tabla 2 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 MD5 de 128 bits codificado en base64 del mensaje de acuerdo con RFC 1864. Este parámetro puede estar vacío.

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.

Expires

Tiempo de caducidad de la autorización temporal, es decir, el valor del parámetro Expires en el mensaje de solicitud: ExpiresValue.

CanonicalizedHeaders

Campo de encabezado de solicitud OBS en un encabezado de solicitud HTTP, que hace referencia a los campos de encabezado iniciados con x-obs-, por ejemplo, x-obs-date, x-obs-acl y x-obs-meta-*.

  1. Todos los caracteres de las palabras clave del campo de encabezado deben convertirse en letras minúsculas. Si una solicitud contiene varios campos de encabezado, estos campos deben organizarse por palabras 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 en el frente de name) debe cambiarse a x-obs-meta-name:name.
  5. Cada campo de encabezado ocupa una línea separada.

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, incluyendo 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, 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.

La firma se genera como sigue basándose en el StringToSign y el SK. El algoritmo de código de autenticación de mensajes basado en hash (HMAC algorithm) se usa para generar la firma.

Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) )

El método para calcular la firma transportada en el URL es diferente del método para calcular la firma de autorización transportada en un encabezado.

  • La firma en el URL debe codificarse usando el URL después de la codificación Base64.
  • Expires en StringToSign corresponde a Date en la información de autorización.

Genere una instancia de URL predefinida para el navegador llevando la firma en el URL.

Tabla 3 Solicitud que tiene la firma incluida en el URL y el StringToSign

Encabezados de solicitud

StringToSign

GET /objectkey?AccessKeyId=MFyfvK41ba2giqM7Uio6PznpdUKGpownRZlmVmHc&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D HTTP/1.1

Host: examplebucket.obs.region.myhuaweicloud.com

GET \n

\n

\n

1532779451\n

/examplebucket/objectkey

Tabla 4 Solicitud de descarga de objetos que tiene el AK/SK temporal y el token de seguridad incluido en el URL y el StringToSign

Encabezado de la solicitud

StringToSign

GET /objectkey?AccessKeyId=MFyfvK41ba2giqM7Uio6PznpdUKGpownRZlmVmHc&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D&x-obs-security-token=YwkaRTbdY8g7q.... HTTP/1.1

Host: examplebucket.obs.region.myhuaweicloud.com

GET \n

\n

\n

1532779451\n

/examplebucket/objectkey?x-obs-security-token=YwkaRTbdY8g7q....

Regla de cálculo de la firma

Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) )

Calcule la firma y utilice el host como prefijo de el URL para generar una URL predefinida:

http(s)://examplebucket.obs.region.myhuaweicloud.com/objectkey?AccessKeyId=AccessKeyID&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D

Si introduce la dirección en el navegador, entonces se puede descargar la objectkey en el bucket de examplebucket. El período de validez de este enlace es 1532779451 (indicando el sáb Jul 28 20:04:11 CST 2018).

En el sistema operativo Linux, al ejecutar el comando curl, debe agregar una barra diagonal (\) para escapar del carácter (&). El siguiente comando puede descargar el objeto objectkey en el archivo output:

curl http(s)://examplebucket.obs.region.myhuaweicloud.com/objectkey?AccessKeyId=AccessKeyID\&Expires=1532779451\&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D -X GET -o output

Si desea utilizar el URL predefinida generada por la firma incluida en el URL del navegador, no utilice Content-MD5, Content-Type o CanonicalizedHeaders que solo se pueden incluir en el encabezado para calcular la firma. De lo contrario, el navegador no puede llevar estos parámetros. Después de enviar la solicitud al servidor, se muestra un mensaje que indica que la firma es incorrecta.

Algoritmo de firma 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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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", "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", "x-obs-security-token"));
	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", "/");
    }
	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 String getURL(String endpoint, Map<String, String> queries,
                String bucketName, String objectName, String signature, long expires) {
                StringBuilder URL = new StringBuilder();
                URL.append("https://").append(bucketName).append(".").append(endpoint).append("/").        
                    append(objectName).append("?");    
                String key;    
                for (Map.Entry<String, String> entry : queries.entrySet()) {        
                    key = entry.getKey();        
                    if (key == null) {
                        continue;        
                    }
                    if (SUB_RESOURCES.contains(key)) {            
                        String value = entry.getValue();            
                        URL.append(key);            
                        if (value != null) {                
                            URL.append("=").append(value).append("&");            
                        } else {                
                            URL.append("&");            
                        }        
                    }    
                }    
                URL.append("AccessKeyId=").append(this.ak).append("&Expires=").append(expires).        
                    append("&Signature=").append(signature);    
                return URL.toString();
        }

	public static void main(String[] args) throws Exception {
		SignDemo demo = new SignDemo();
		demo.ak = "<your-access-key-id>";
		demo.sk = "<your-secret-key-id>";
                String endpoint = "<your-endpoint>";
		String bucketName = "bucket-test";
		String objectName = "hello.jpg";
// A header cannot be carried if you want to use a URL to access OBS through a browser. If a header is added to headers, the signature does not match. To use the headers, it must be processed by the client.
		Map<String, String[]> headers = new HashMap<String, String[]>();
		Map<String, String> queries = new HashMap<String, String>();
                //  Expiration time of the request message. Set it to expire in 24 hours.
                long expires = (System.currentTimeMillis() + 86400000L) / 1000;
                String signature = demo.querySignature("GET", headers, queries, bucketName, objectName, expires);
                System.out.println(signature);
                String URL = demo.getURL(endpoint, queries, bucketName, objectName, signature, expires);
                System.out.println(URL);
	}
}

Algoritmo de firma en el lenguaje de programación C

Haga clic aquí para descargar el código de ejemplo 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 código de ejemplo para calcular la firma está contenido en el archivo de encabezado main.c.

Manejo de errores de coincidencia de firmas

Durante una llamada 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. Compruebe su clave y método de firma.

Maneje el problema refiriéndose a ¿Por qué no coinciden las firmas?