九游平台/ 对象存储服务 obs/ / / / 基于浏览器上传的表单中携带签名
更新时间:2024-08-07 gmt 08:00

基于浏览器上传的表单中携带签名-九游平台

功能介绍

obs九游平台的服务支持基于浏览器的post上传对象请求,此类请求的签名信息通过表单的方式上传。计算post表单上传请求的签名,需要先定义一个安全策略(policy),这个安全策略的作用是限制表单上传的内容,例如规定表单上传对象的对象名前缀必须以“prefix01”开头,使用policy能够帮助您更好的管控桶中的文件。post上传对象的流程是:

  1. 创建一个policy,指定请求中需要满足的条件,比如:桶名、对象名前缀。
  2. 计算基于此policy的签名。
  3. 创建一个表单,表单中必须包含有效的签名和policy,使用该表单将对象上传到桶中。

步骤一:创建policy

下面将以如下policy为例介绍policy的组成和语法。下方的policy限制了签名的有效时间为2024年12月31日12点之后无效,上传对象的访问权限限制为公共读,请求消息头中的x-obs-security-token须等于"ywkartbdy8g7q...." },对象上传的桶名需要为“book”,对象名必须以前缀“user/”开头:

1
2
3
4
5
6
7
8
{"expiration":"2024-12-31t12:00:00.000z",
"conditions":[
{"x-obs-acl":"public-read"},
{"x-obs-security-token":"ywkartbdy8g7q...."},
{"bucket":"book"},
["starts-with","$key","user/"]
]
}

policy策略中由有效时间有效时间:expiration和条件元素条件元素:conditions两部分组成。

有效时间:expiration

表1 有效时间expiration

参数名称

参数类型

是否必选

描述

expiration

string

必选

参数解释:

描述本次签名的有效时间。如示例中"expiration": "2024-12-31t12:00:00.000z"表示请求在2024年12月31日12点之后无效。

约束限制:

格式为iso 8601 utc,"yyyy-mm-dd't'hh:mm:ss'z'"或"yyyy-mm-dd't'hh:mm:ss.sss'z'"。

取值范围:

不涉及

默认取值:

条件元素:conditions

使用conditions可以要求调用请求必须满足指定的条件限制。示例中的条件要求请求的桶名必须是book,对象名必须以user/为前缀,对象的acl必须是公共可读。policy可以限制除了accesskeyid、signature、file、policy、token、field names以及前缀为"x-ignore-"外的表单中的其他所有项。下表是conditions中可校验的元素:

表2 policy中可以包含的条件元素

元素名称

元素类型

描述

支持的匹配方式

x-obs-acl

string

请求中的acl。

精确匹配:exact matches

前缀匹配:starts-with

content-length-range

int

设置上传对象的最大和最小长度。例如:

1
["content-length-range",1048576,10485760]

范围匹配:specifying ranges

cache-control, content-type, content-disposition, content-encoding, expires

string

rest请求特定头域。

精确匹配:exact matches

前缀匹配:starts-with

key

string

上传对象的名字。

精确匹配:exact matches

前缀匹配:starts-with

bucket

string

请求桶名。

精确匹配:exact matches

success_action_redirect

string

上传对象成功后重定向的url地址。具体描述请参见5.4.2-post上传

精确匹配:exact matches

前缀匹配:starts-with

success_action_status

string

如果未指定success_action_redirect,则成功上传时返回给客户端的状态码。具体描述请参见5.4.2-post上传

精确匹配:exact matches

x-obs-meta-*

string

用户自定义元数据。

元素中的关键字不允许含有非ascii码或不可识别字符,如果一定要使用非ascii码或不可识别字符,需要客户端自行做编解码处理,可以采用url编码或者base64编码,服务端不会做解码处理。

精确匹配:exact matches

前缀匹配:starts-with

x-obs-*

string

其他以x-obs-为前缀的头域。

精确匹配:exact matches

前缀匹配:starts-with

x-obs-security-token

string

请求消息头中字段名。

临时ak/sk和securitytoken鉴权必加字段名。如何获取临时ak/sk和securitytoken请参考。

精确匹配:exact matches

policy条件匹配的方式如下:

表3 policy条件匹配方式

匹配方式

描述

精确匹配(exact matches)

默认是完全匹配,post表单中该项的值必须和policy的conditions中设置的值完全一样。例如:上传对象的同时设置对象acl为public-read,表单中x-obs-acl元素的值为public-read,policy中的conditions可以设置为

1
{"x-obs-acl":"public-read"}

或者另一种等效的格式:

1
["eq","$x-obs-acl","public-read"]

前缀匹配(starts with)

如果使用该条件,则post表单中对应元素的值必须是固定字符串开始。例如:上传对象名以user/为前缀,表单中key元素的值可以是user/test1、user/test2,policy的conditions中该条件如下:

1
["starts-with","$key","user/"]

任意匹配(matching any content)

post表单中对应元素的值可以是任意值。例如:请求成功后重定向的地址可以是任意地址,表单中success_action_redirect元素的值可以是任意值,policy的conditions中该条件如下:

1
["starts-with","$success_action_redirect",""]

范围匹配(specifying ranges)

post表单中file元素文件的内容长度可以是一个指定的范围,只用于限制对象大小。例如上传对象大小为1-10mb,表单中file元素的内容长度可以是1048576-10485760,policy的conditions中该条件如下,注意值没有双引号:

1
["content-length-range",1048576,10485760]

policy使用json格式,conditions可以支持 { } 和 [ ] 两种方式,{ }中包含表单元素的key和value两项,以冒号分隔;[ ]中包含条件类型、key、value三项,以逗号分隔,元素key之前使用$字符表示变量。

policy中必须转义的字符如下:

表4 policy中必须转义的字符

转义后的字符

真实字符

\\

反斜杠(\)

\$

美元符号($)

\b

退格

\f

换页

\n

换行

\r

回车

\t

水平制表

\v

垂直制表

\uxxxx

所有unicode字符

步骤二:计算签名

obs有2种计算表单中携带签名的方式:

表5 计算表单签名的2种方式

计算方式

描述

更多参考

sdk

obs所有语言的sdk都已实现表单上传签名,无需手动进行签名计算。推荐您直接使用sdk进行接口调用,更方便快捷。

sdk签名实现

手动编码计算签名

按照签名算法手动编码计算签名。

签名算法

sdk签名实现

表6 obs sdk header携带签名的实现

sdk

签名实现源文件

java

python

go

c

-

nodejs

browserjs

php

.net

-

签名算法

表单中携带签名的计算公式如下:

signature = base64( hmac-sha1( yoursecretaccesskeyid, stringtosign ) )
stringtosign = base64( utf-8-encoding-of( policy ) )

签名的计算过程如下:

  1. 构造请求字符串stringtosign,即对policy先进行utf8编码,然后再进行base64编码。
  2. 使用sk第一步结果进行hmac-sha1签名计算。
  3. 对第二步的结果进行base64编码,得到签名。
图1 计算表单签名

签名代码示例

以下是计算表单中携带签名的示例代码:

 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
importjava.text.simpledateformat;
importjava.util.arraylist;
importjava.util.base64;
importjava.util.collections;
importjava.util.date;
importjava.util.list;
importjava.util.timezone;

importjavax.crypto.mac;
importjavax.crypto.spec.secretkeyspec;

publicclass signdemo{

privatestaticfinalstringdefault_encoding="utf-8";
privatestaticfinalstringexpiration_date_formatter="yyyy-mm-dd't'hh:mm:ss.sss'z'";
privatestaticfinaltimezonegmt_timezone=timezone.gettimezone("gmt");
privatestaticfinallongdefault_expire_seconds=300;

privatestringak;
privatestringsk;

privatestringjoin(listitems){
stringbuildersb=newstringbuilder();
for(inti=0;i<items.size();i){
stringitem=items.get(i).tostring();sb.append(item);
if(i<items.size()-1){
sb.append(",");
}
}
returnsb.tostring();
}
// 构造stringtosign
privatestringstringtosign(string[]tmpconditions,stringexpiration){
listconditions=newarraylist<>();
collections.addall(conditions,tmpconditions);
return"{\"expiration\":""\""expiration"\",""\"conditions\":["join(conditions)"]}";
}
privatestringgetformatexpiration(daterequestdate,longexpires){
requestdate=requestdate!=null?requestdate:newdate();
simpledateformatexpirationdateformat=newsimpledateformat(expiration_date_formatter);
expirationdateformat.settimezone(gmt_timezone);
dateexpirydate=newdate(requestdate.gettime()(expires<=0?default_expire_seconds:expires)*1000);
returnexpirationdateformat.format(expirydate);
}
// 计算签名
publicstringpostsignature(stringpolicy)throwsexception{
byte[]policybase64=base64.getencoder().encode(policy.getbytes(default_encoding));
secretkeyspecsigningkey=newsecretkeyspec(this.sk.getbytes(default_encoding),"hmacsha1");
macmac=mac.getinstance("hmacsha1");mac.init(signingkey);
returnbase64.getencoder().encodetostring(mac.dofinal(policybase64));
}
publicstaticvoidmain(string[]args)throwsexception{
signdemodemo=newsigndemo();
/* 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
        本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量huaweicloud_sdk_ak和huaweicloud_sdk_sk。*/
demo.ak=system.getenv("huaweicloud_sdk_ak");
demo.sk=system.getenv("huaweicloud_sdk_sk");
stringauthexpiration=demo.getformatexpiration(null,0);
string[]tmpconditions={"{\"bucket\": \"bucketname\" }","[\"starts-with\", \"$key\", \"obj\"]"};
stringpolicy=demo.stringtosign(tmpconditions,authexpiration);
stringpolicybase64=base64.getencoder().encodetostring(policy.getbytes(default_encoding));
stringsignature=demo.postsignature(policy);
// 表单中携带accesskeyid、policy、signature的签名 
system.out.println("authexpiration="authexpiration);
system.out.println("policy="policy);
system.out.println("policybase64="policybase64);
system.out.println("signature="signature);
// 表单中携带token的签名 
system.out.println("token="demo.ak":"signature":"policybase64);
}
}
 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
# coding=utf-8
import binascii
import hashlib
import hmac
import os
import time
from datetime import datetime
import pytz
class signaturedemo:
    expiration_date_formatter = "%y-%m-%dt%h:%m:%s.%f"
    default_encoding = "utf-8"
    # 默认过期时间五分钟
    default_expire_seconds = 300
    gmt_timezone = "gmt"
    def __init__(self, ak=none, sk=none):
        self.ak = ak
        self.sk = sk
    # request_date和expires是时间戳形式,例如:1675651495.979
    def get_format_expiration(self, request_date, expires):
        request_date = request_date if request_date else time.time()
        expiry_date = request_date  (expires if expires > 0 else self.default_expire_seconds)
        expiration = datetime.fromtimestamp(expiry_date, pytz.timezone(self.gmt_timezone)).strftime(
            self.expiration_date_formatter)[:-3]  "z"
        return expiration
    def post_signature(self, policy):
        # 如果使用binascii或encode("base64"), 需要去除换行符
        policy_base64 = binascii.b2a_base64(policy.encode(self.default_encoding)).rstrip()
        hashed = hmac.new(self.sk.encode(self.default_encoding), policy_base64, hashlib.sha1)
        return binascii.b2a_base64(hashed.digest()).rstrip()
    @staticmethod
    def string_to_sign(conditions, expiration):
        return "{\"expiration\":"  "\""  expiration  "\","  "\"conditions\":["  ",".join(conditions)  "]}"
if __name__ == "__main__":
    demo = signaturedemo()
    # 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
    # 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量huaweicloud_sdk_ak和huaweicloud_sdk_sk。
    demo.ak = os.getenv('huaweicloud_sdk_ak')
    demo.sk = os.getenv('huaweicloud_sdk_sk')
    auth_expiration = demo.get_format_expiration(none, 0)
    conditions_example = [
        "{\"bucket\": \"bucketname\" }",
        "[\"starts-with\", \"$key\", \"obj\"]"
    ]
    post_policy = demo.string_to_sign(conditions_example, auth_expiration)
    policy_base64 = binascii.b2a_base64(post_policy.encode(demo.default_encoding)).rstrip()
    signature = demo.post_signature(post_policy)
    # 表单中携带accesskeyid、policy、signature的签名
    print("authexpiration="  auth_expiration)
    print("policy="  post_policy)
    print("policybase64="  policy_base64)
    print("signature="  signature)
    # 表单中携带token的签名
    print("token="  demo.ak  ":"  signature  ":"  policy_base64)

相关文档

网站地图