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.
Atualizado em 2022-11-07 GMT+08:00

Autenticação de assinatura em um URL

O OBS permite que os usuários criem uma URL para uma operação específica. A URL contém informações como AK do usuário, assinatura, período de validade e recursos. Qualquer usuário que obtenha o URL pode executar a operação. Após receber o pedido, o OBS considera que a operação é realizada pelo usuário que emite a URL. Por exemplo, se a URL de uma solicitação de download de objeto carrega informações de assinatura é construída, o usuário que obtém a URL pode baixar o objeto, mas a URL é válida apenas dentro do tempo de expiração especificado pelo parâmetro de Expires. O URL que transporta a assinatura é usado para permitir que outras pessoas usem o URL pré-emitido para autenticação de identidade quando o SK não for fornecido e executem a operação predefinida.

O formato da mensagem que contém a solicitação de assinatura no URL é o seguinte:

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

O formato da mensagem que contém o AK/SK temporário e o token de segurança no URL para download de objetos é o seguinte:

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

Tabela 1 descreve os parâmetros.

Tabela 1 Parâmetros de solicitação

Parâmetro

Descrição

Obrigatório

AccessKeyId

AK informações do emissor. O OBS determina a identidade do emissor com base no AK e considera que o URL é acessado pelo emissor.

Tipo: string

Sim

Expires

Indica quando o URL autorizado temporariamente expira, em segundos. A hora deve estar no formato de Tempo Universal Coordenado (UTC) e posterior às 00:00:00 de 1o de janeiro de 1970.

Tipo: string

Sim

Signature

A assinatura gerada usando o SK e o tempo de expiração.

Tipo: string

Sim

x-obs-security-token

Durante a autenticação temporária, o AK/SK temporário e o token de segurança devem ser usados ao mesmo tempo e o campo x-obs-security-token deve ser adicionado ao cabeçalho da solicitação.

Para obter detalhes sobre como obter um par AK/SK temporário e um token de segurança, consulte Obtenção de um par AK/SK temporário e um token de segurança.

Não

O processo de computação de assinatura é o seguinte:

1. Construa 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 obtido na etapa 3.

5. Execute a codificação de URL no resultado da etapa 4 para obter a assinatura.

O StringToSign é construído de acordo com as seguintes regras. Tabela 2 descreve os parâmetros.

StringToSign = 
     HTTP-Verb + "\n" +   
     Content-MD5 + "\n" +   
     Content-Type + "\n" +   
     Expires + "\n" +   
     CanonicalizedHeaders +   CanonicalizedResource; 
Tabela 2 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-encoded 128-bit MD5 digest of the message according to RFC 1864. Este parâmetro pode estar vazio.

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.

Expires

Tempo de expiração da autorização temporária, ou seja, o valor do parâmetro Expires na mensagem de solicitação: ExpiresValue.

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 no campo de cabeçalho devem ser convertidos em letras minúsculas. Se uma solicitação contém vários campos de cabeçalho, esses campos devem ser organizados por palavras-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.

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.

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 = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) )

O método para calcular a assinatura realizada na URL é diferente do método para calcular a assinatura de autorização realizada em um cabeçalho.

  • A assinatura no URL deve ser codificada usando o URL após a codificação Base64.
  • Expires em StringToSign corresponde a Date nas informações de autorização.

Gere uma instância de URL predefinida para o navegador carregando a assinatura no URL.

Tabela 3 Solicitação que tem a assinatura transportada no URL e no StringToSign

Cabeçalhos de solicitação

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

Tabela 4 Solicitação de download de objeto que tem o AK/SK temporário e o token de segurança transportados no URL e no StringToSign

Cabeçalho da solicitação

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

Regra de cálculo da assinatura

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

Calcule a assinatura e use o host como o prefixo do URL para gerar um URL predefinido:

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

Se você inserir o endereço no navegador, o objeto objectkey no bucket examplebucket poderá ser baixado. O período de validade deste link é 1532779451 (indicando Sat Jul 28 20:04:11 CST 2018).

No sistema operacional Linux, ao executar o comando curl, você precisa adicionar uma barra (\) para escapar do caractere (&). O comando a seguir pode baixar o objeto objectkey para o arquivo de output:

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

Se quiser usar o URL predefinido gerado pela assinatura transportada no URL no navegador, não use Content-MD5, Content-Type ou CanonicalizedHeaders que só podem ser carregados no cabeçalho para calcular a assinatura. Caso contrário, o navegador não pode carregar esses parâmetros. Depois que a solicitação é enviada para o servidor, uma mensagem é exibida indicando que a assinatura está incorreta.

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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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 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 do 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?