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.
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 |
Sí |
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 |
Sí |
Signature |
La firma generada usando el SK y el tiempo de caducidad. Tipo: string |
Sí |
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;
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-*.
|
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:
|
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.
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 |
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
![](https://support.huaweicloud.com/intl/es-us/api-obs/public_sys-resources/note_3.0-es-us.png)
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.
- La API para calcular la firma está contenida en el archivo de encabezado sign.h.
- 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?