Este conteúdo foi traduzido por máquina para sua conveniência e a Huawei Cloud não pode garantir que o conteúdo foi traduzido com precisão. Para exibir o conteúdo original, use o link no canto superior direito para mudar para a página em inglês.
Central de ajuda/ Object Storage Service/ Referência de API/ Chamada das API/ Autenticação/ Autenticação de assinatura em um cabeçalho
Atualizado em 2022-11-14 GMT+08:00

Autenticação de assinatura em um cabeçalho

Para todas as operações de API, a autenticação de identidade mais comum é transportar assinaturas em cabeçalhos.

No cabeçalho, a assinatura é realizada na autorização cabeçalho campo da mensagem HTTP. O formato do cabeçalho da mensagem é o seguinte:

Autorização: OBS AccessKeyID:assinatura

O processo do algoritmo de assinatura é o seguinte:

1. Construa a cadeia de caracteres de solicitação (StringToSign).

2. Execute a codificação UTF-8 no resultado obtido na etapa anterior.

3. Use o SK para executar o cálculo da assinatura HMAC-SHA1 no resultado obtido na etapa 2.

4. Execute a codificação Base64 no resultado da etapa 3 para obter a assinatura.

O StringToSign é construído de acordo com as seguintes regras. Tabela 1 descreve os parâmetros.
StringToSign = 
    HTTP-Verb + "\n" + 
    Content-MD5 + "\n" + 
    Content-Type + "\n" + 
    Date + "\n" + 
    CanonicalizedHeaders + CanonicalizedResource
Tabela 1 Parâmetros necessários para a construção de um StringToSign

Parâmetro

Descrição

HTTP-Verb

Indica um método de solicitação HTTP compatível com a API REST do OBS. O valor pode ser um verbo HTTP como PUT, GET, ou DELETE.

Content-MD5

Base64-codificado 128-bit MD5 resumo da mensagem de acordo com RFC 1864. Este parâmetro pode estar vazio. Para obter detalhes, consulte Tabela 6 e os exemplos de algoritmo abaixo da tabela.

Content-Type

Especifica o tipo de mensagem, por exemplo, text/plain.

Se uma solicitação não contiver esse campo de cabeçalho, esse parâmetro será considerado como uma string vazia. Para mais detalhes, consulte Tabela 2.

Date

Hora em que uma solicitação é iniciada. Este parâmetro usa o formato de hora RFC 1123. Se o desvio entre a hora especificada por esse parâmetro e a hora do servidor for superior a 15 minutos, o servidor retornará o erro 403.

Este parâmetro é uma cadeia de caracteres vazia quando o x-obs-date é especificado. Para mais detalhes, consulte Tabela 6.

Se uma operação (por exemplo, a obtenção de um conteúdo objeto) for temporariamente autorizada, este parâmetro não será necessário.

CanonicalizedHeaders

Campo de cabeçalho de solicitação OBS em um cabeçalho de solicitação HTTP, referindo-se a campos de cabeçalho iniciados com x-obs-, por exemplo, x-obs-date, x-obs-acl, e x-obs-meta-*.

  1. Todos os caracteres de palavras-chave em um campo de cabeçalho de solicitação devem ser convertidos em letras minúsculas (os valores de conteúdo devem diferenciar maiúsculas de minúsculas, por exemplo, x-obs-storage-class:STANDARD). Se uma solicitação contiver vários campos de cabeçalho, esses campos devem ser organizados por palavra-chave em ordem alfabética de a a z.
  2. Se vários campos de cabeçalho em uma solicitação tiverem o mesmo prefixo, combine os campos de cabeçalho em um. Por exemplo, x-obs-meta-name:name1 e x-obs-meta-name:name2 devem ser reorganizados em x-obs-meta-name:name1,name2. Use vírgula para separar os valores.
  3. Palavras-chave no campo do cabeçalho da solicitação não podem conter caracteres não-ASCII ou irreconhecíveis, que também não são aconselháveis para valores no campo do cabeçalho da solicitação. Se os dois tipos de caracteres são necessários, eles devem ser codificados e decodificados no lado do cliente. A codificação URL ou a codificação Base64 são aceitáveis, mas o servidor não realiza a decodificação.
  4. Exclua espaços e tabulações sem sentido em um campo de cabeçalho. Por exemplo, x-obs-meta-name: name (com um espaço sem sentido na frente do name) deve ser alterado para x-obs-meta-nome: nome.
  5. Cada campo de cabeçalho ocupa uma linha separada. Consulte Tabela 4.

CanonicalizedResource

Indica o recurso OBS especificado por uma solicitação HTTP. Este parâmetro é construído da seguinte forma:

<Bucket name + Object name> + [Subresource 1] + [Subresource 2] + ...

  1. Nome do bucket e nome do objeto
    • Se o bucket estiver vinculado a um nome de domínio de usuário, use o nome de domínio de usuário como o nome do bucket, por exemplo, /obs.ccc.com/object. obs.ccc.com é o nome de domínio do usuário vinculado ao bucket. Se nenhum nome de objeto for especificado, o bucket inteiro será listado, por exemplo, /obs.ccc.com/.
    • Se você não acessar o OBS usando um nome de domínio de usuário, esse campo estará no formato de /bucket/object. Se nenhum nome de objeto for especificado, por exemplo,/bucket/, o bucket inteiro será listado. Se o nome do bucket também não for especificado, o valor desse campo será /.
  2. Se um sub-recurso (como ?acl e ?logging) existe, o sub-recurso deve ser adicionado.

    OBS suporta uma variedade de sub-recursos, incluindo CDNNotifyConfiguração, 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, e x-obs-security-token.

  3. Se houver vários sub-recursos, classifique-os em ordem alfabética de a a z e use & para combinar os sub-recursos.
NOTA:
  • Um sub-recurso é único. Não adicione sub-recursos com a mesma palavra-chave (por exemplo, key=value1&key=value2) no mesmo URL de solicitação. Neste caso, a assinatura é calculada apenas com base no primeiro sub-recurso, e apenas o valor do primeiro sub-recurso tem efeito sobre o serviço real.
  • Usando a API GetObject como exemplo, suponha que haja um bucket chamado bucket-test e um objeto chamado object-test no bucket, e a versão do objeto seja xxx. Ao obter o objeto, você precisa reescrever Content-Type para text/plain. Então, o CanonicalizedResource calculado pela assinatura é /bucket-test/object-test?response-content-type=text/plain&versionId=xxx.

As tabelas a seguir fornecem alguns exemplos de geração de StringToSign.

Tabela 2 Obtenção de um objeto

Cabeçalho da solicitação

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

Tabela 3 Uso de AK/SK temporária e token de segurança para carregar objetos

Cabeçalho de solicitação

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

Tabela 4 Uma solicitação de upload de objeto contendo campos de cabeçalho

Cabeçalho da solicitação

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

Tabela 5 Obtenção de uma ACL de objeto

Cabeçalho da solicitação

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

Tabela 6 Uma solicitação de upload de objeto carregando o cabeçalho Content-MD5

Cabeçalho da solicitação

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

Tabela 7 Carregamento de um objeto por meio de um nome de domínio de usuário

Cabeçalho da solicitação

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 em 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();
          }
     }
}
A assinatura é gerada da seguinte forma com base no StringToSign e SK. O algoritmo de código de autenticação de mensagem baseado em hash (algoritmo HMAC) é usado para gerar a assinatura.
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )

Por exemplo, para criar um bucket privado chamado newbucketname2 em uma região, o formato de solicitação do cliente é o seguinte:
PUT / HTTP/1.1 
Host: newbucketname2.obs.region.myhuaweicloud.com
Content-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>

Algoritmo de assinatura em 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
211
212
213
214
215
216
217
218
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", "/")
        .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());
				}
			}
		}
		
//		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();
		demo.ak = "<your-access-key-id>";
		demo.sk = "<your-secret-key-id>";
		
		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));
	}
	
}

O resultado do cálculo da assinatura é o seguinte (varia com o tempo de execução): YdH8ffpcbS6YpeOMcEZfn0wE90c=

Algoritmo de assinatura em Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import sys
import hashlib
import hmac
import binascii
from datetime import datetime
IS_PYTHON2 = sys.version_info.major == 2 or sys.version < '3'

yourSecretAccessKeyID = '275hSvB6EEOorBNsMDEfOaICQnilYaPZhXUaSK64'
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

O resultado do cálculo da assinatura é o seguinte (varia com o tempo de execução): YdH8ffpcbS6YpeOMcEZfn0wE90c=

Algoritmo de assinatura na linguagem de programação C

Clique aqui para baixar o código de exemplo para calcular a assinatura na linguagem de programação C.

  1. A API para calcular a assinatura está contida no arquivo de cabeçalho sign.h.
  2. O código de exemplo para calcular a assinatura está contido no arquivo de cabeçalho main.c.

Tratamento de erros de incompatibilidade de assinatura

Durante uma chamada de API de OBS, se o seguinte erro for relatado,

Código de status: 403 Proibido

Código de erro: SignatureDoesNotMatch

Mensagem de erro: A assinatura da solicitação que calculamos não corresponde à assinatura que você forneceu. Verifique sua chave e método de assinatura.

Lide com o problema consultando Por que as assinaturas não combinam?