Authentication of Signature Carried in the Table Uploaded Through a Browser
OBS supports browser-based object upload using the POST method. Signatures of such requests are uploaded in tables. First, create a security policy and specify the requirements in the request, for example, bucket name and object name prefix. Then, create a signature based on this policy. The request form to be signed must contain valid signature and policy. Finally, create a table to upload the object to the bucket.
The process of calculating a signature is as follows:
- Encode the policy content in UTF-8.
- Encode the result of 1 in Base64.
- Use the SK to calculate the HMAC-SHA1 signature on the result of 2.
- Encode the result of 3 in Base64 to obtain the signature.
StringToSign = Base64( UTF-8-Encoding-Of( policy ) ) Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, StringToSign ) )
{ "expiration": "2017-12-31T12:00:00.000Z", "conditions": [ {"x-obs-acl": "public-read" }, {"x-obs-security-token": "YwkaRTbdY8g7q...." }, {"bucket": "book" }, ["starts-with", "$key", "user/"] ] }
The policy contains the validity period (see Expiration) and conditions (see Conditions).
Expiration
The expiration field describes when the signature will expire, which is expressed in the format according to ISO 8601 UTC. For example, expiration: 2017-12-31T12:00:00.000Z in the example means that the request becomes invalid after 12:00:00 on December 31, 2017. This field must be specified in a policy. It can only be in the yyyy-MM-dd'T'HH:mm:ss'Z' or yyyy-MM-dd'T'HH:mm:ss.SSS'Z' format.
Conditions
A mechanism used to verify the validity of a request. Conditions are used to define the content that must be contained in a request. In the example, the requested bucket name is book, the object name is prefixed with user/, and the ACL of the object is public read. All items in the form, excluding AccessKeyId, signature, file, policy, token, field names, and the prefix x-ignore-, must be included in the policy. The following table lists the items that should be contained in Conditions.
Element |
Description |
---|---|
x-obs-acl |
ACL in the request. Supports exact match and conditional match such as starts-with. |
content-length-range |
Maximum and minimum length of an object to be uploaded. The value can be a range. |
Cache-Control, Content-Type, Content-Disposition, Content-Encoding, Expires |
Headers specially for REST requests Supports exact match and conditional match such as starts-with. |
key |
Name of an object to be uploaded. Supports exact match and conditional match such as starts-with. |
bucket |
Name of the requested bucket. Supports exact match. |
success_action_redirect |
Redirection address after the upload is successful. For details, see Uploading an Object - POST. Supports exact match and conditional match such as starts-with. |
success_action_status |
If success_action_redirect is not specified, the status code is returned to the client when the upload is successful. For details, see Uploading an Object - POST. Supports exact match. |
x-obs-meta-* |
User-defined metadata. Keywords in an element cannot contain non-ASCII or unrecognizable characters. If non-ASCII or unrecognizable characters are necessary, they should be encoded and decoded on the client side. Either URL encoding or Base64 encoding is acceptable, but the server does not perform decoding. Supports exact match and conditional match such as starts-with. |
x-obs-* |
Other header fields with prefix x-obs-. Supports exact match and conditional match such as starts-with. |
x-obs-security-token |
Field name in the request header. Mandatory field for the temporary AK/SK and security token authentication. |
The table below describes how policy conditions can be matched.
Matching Method |
Description |
---|---|
Exact Matches |
Exact match by default. The value in the POST table must be the same as that in the policy. For example, if object ACL is set to public-read when the object is uploaded, the value of the x-obs-acl element in the table is public-read. Therefore, the conditions in the policy can be set to {"x-obs-acl": "public-read"} or ["eq", "$x-obs-acl", "public-read"], which are equivalent. |
Starts With |
If this condition is used, the value set in the POST table must start with a fixed character string. For example, if the name of uploaded objects must be prefixed with user/, the value of the key element in the table can be user/test1, user/test2, and so on. Therefore, conditions in the policy can be set to: ["starts-with", "$key", "user/"] |
Matching Any Content |
The corresponding element in the POST table can be any value. For example, if the redirection address upon request success can be any address, the value of the success_action_redirect element in the table can be any value. Therefore, conditions in the policy can be set to: ["starts-with", "$success_action_redirect", ""] |
Specifying Ranges |
The content length of the file element in the POST table can be a specified range and is used only to limit the object size. For example, if the size of the uploaded object is between 1 MB to 10 MB, the content length of the file element in the table can be from 1048576 to 10485760. Therefore, conditions in the policy can be set to (the value does not contain quotation marks) ["content-length-range", 1048576, 10485760] |
A policy is in the JSON format. Conditions can be put in curly brackets {} and square brackets []. The key and value elements of the table are written in the curly brackets {}, which are separated by colons (:). The square brackets [] contain the condition type, key, and value. These three items are separated by commas (,). The dollar sign ($) in front of the key indicates that the key is a variable.
The table below lists the characters that must be escaped in a policy.
Character After Escape |
Real Character |
---|---|
\\ |
Backslash (\) |
\$ |
Dollar symbol ($) |
\b |
Backspace |
\f |
Page up and down |
\n |
Newline characters |
\r |
Enter |
\t |
Horizontal table |
\v |
Vertical table |
\uxxxx |
All Unicode characters |
Request and Policy Examples
The following tables provide examples of requests and policies.
Example 1: Upload the testfile.txt object to bucket examplebucket and set the object ACL to public-read.
Request |
Policy |
---|---|
POST / HTTP/1.1 Host: examplebucket.obs.region.myhuaweicloud.com Content-Type: multipart/form-data; boundary=7e32233530b26 Content-Length: 1250 --7e32233530b26 Content-Disposition: form-data; name="key" testfile.txt --7e32233530b26 Content-Disposition: form-data; name="x-obs-acl" public-read --7e32233530b26 Content-Disposition: form-data; name="content-type" text/plain --7e32233530b26 Content-Disposition: form-data; name="AccessKeyId" UDSIAMSTUBTEST000002 --7e32233530b26 Content-Disposition: form-data; name="policy" ewogICJleHBpcmF0aW9uIjogIjIwMTktMDctMDFUMTI6MDA6MDAuMDAwWiIsCiAgImNvbmRpdGlvbnMiOiBbCiAgICB7ImJ1Y2tldCI6ICJleGFtcGxlYnVja2V0IiB9LAogICAgWyJlcSIsICIka2V5IiwgInRlc3RmaWxlLnR4dCJdLAoJeyJ4LW9icy1hY2wiOiAicHVibGljLXJlYWQiIH0sCiAgICBbImVxIiwgIiRDb250ZW50LVR5cGUiLCAidGV4dC9wbGFpbiJdLAogICAgWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsIDYsIDEwXQogIF0KfQo= --7e32233530b26 Content-Disposition: form-data; name="signature" xxl7bZs/5FgtBUggOdQ88DPZUo0= --7e32233530b26 Content-Disposition: form-data; name="file"; filename="E:\TEST_FILE\TEST.txt" Content-Type: text/plain 123456 --7e32233530b26 Content-Disposition: form-data; name="submit" Upload --7e32233530b26-- |
{ "expiration": "2019-07-01T12:00:00.000Z", "conditions": [ {"bucket": "examplebucket" }, ["eq", "$key", "testfile.txt"], {"x-obs-acl": "public-read" }, ["eq", "$Content-Type", "text/plain"] ] } |
Example 2: Upload the file/obj1 object to bucket examplebucket and configure the four custom metadata items of the object.
Request |
Policy |
---|---|
POST / HTTP/1.1 Host: examplebucket.obs.region.myhuaweicloud.com Content-Type: multipart/form-data; boundary=7e329d630b26 Content-Length: 1597 --7e3542930b26 Content-Disposition: form-data; name="key" file/obj1 --7e3542930b26 Content-Disposition: form-data; name="AccessKeyId" UDSIAMSTUBTEST000002 --7e3542930b26 Content-Disposition: form-data; name="policy" ewogICJleHBpcmF0aW9uIjogIjIwMTktMDctMDFUMTI6MDA6MDAuMDAwWiIsCiAgImNvbmRpdGlvbnMiOiBbCiAgICB7ImJ1Y2tldCI6ICJleGFtcGxlYnVja2V0IiB9LAogICAgWyJzdGFydHMtd2l0aCIsICIka2V5IiwgImZpbGUvIl0sCiAgICB7Ingtb2JzLW1ldGEtdGVzdDEiOiJ2YWx1ZTEifSwKICAgIFsiZXEiLCAiJHgtb2JzLW1ldGEtdGVzdDIiLCAidmFsdWUyIl0sCiAgICBbInN0YXJ0cy13aXRoIiwgIiR4LW9icy1tZXRhLXRlc3QzIiwgImRvYyJdLAogICAgWyJzdGFydHMtd2l0aCIsICIkeC1vYnMtbWV0YS10ZXN0NCIsICIiXQogIF0KfQo= --7e3542930b26 Content-Disposition: form-data; name="signature" HTId8hcaisn6FfdWKqSJP9RN4Oo= --7e3542930b26 Content-Disposition: form-data; name="x-obs-meta-test1" value1 --7e3542930b26 Content-Disposition: form-data; name="x-obs-meta-test2" value2 --7e3542930b26 Content-Disposition: form-data; name="x-obs-meta-test3" doc123 --7e3542930b26 Content-Disposition: form-data; name="x-obs-meta-test4" my --7e3542930b26 Content-Disposition: form-data; name="file"; filename="E:\TEST_FILE\TEST.txt" Content-Type: text/plain 123456 --7e3542930b26 Content-Disposition: form-data; name="submit" Upload --7e3542930b26-- |
{ "expiration": "2019-07-01T12:00:00.000Z", "conditions": [ {"bucket": "examplebucket" }, ["starts-with", "$key", "file/"], {"x-obs-meta-test1":"value1"}, ["eq", "$x-obs-meta-test2", "value2"], ["starts-with", "$x-obs-meta-test3", "doc"], ["starts-with", "$x-obs-meta-test4", ""] ] } |
Signature calculation in 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 |
import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.TimeZone; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class SignDemo { private static final String DEFAULT_ENCODING = "UTF-8"; private static final String EXPIRATION_DATE_FORMATTER = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; private static final TimeZone GMT_TIMEZONE = TimeZone.getTimeZone("GMT"); private static final long DEFAULT_EXPIRE_SECONDS = 300; private String ak; private String sk; private String join(List<?> items) { 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(","); } } return sb.toString(); } private String stringToSign(String[] tmpConditions, String expiration) { List<String> conditions = new ArrayList<>(); Collections.addAll(conditions, tmpConditions); return "{\"expiration\":" + "\"" + expiration + "\"," + "\"conditions\":[" + join(conditions) + "]}"; } private String getFormatExpiration(Date requestDate, long expires) { requestDate = requestDate != null ? requestDate : new Date(); SimpleDateFormat expirationDateFormat = new SimpleDateFormat(EXPIRATION_DATE_FORMATTER); expirationDateFormat.setTimeZone(GMT_TIMEZONE); Date expiryDate = new Date(requestDate.getTime() + (expires <= 0 ? DEFAULT_EXPIRE_SECONDS : expires) * 1000); return expirationDateFormat.format(expiryDate); } public String postSignature(String policy) throws Exception { byte[] policyBase64 = Base64.getEncoder().encode(policy.getBytes(DEFAULT_ENCODING)); SecretKeySpec signingKey = new SecretKeySpec(this.sk.getBytes(DEFAULT_ENCODING), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); return Base64.getEncoder().encodeToString(mac.doFinal(policyBase64)); } public static void main(String[] args) throws Exception { SignDemo demo = new SignDemo(); /* Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables. In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK. */ demo.ak = System.getenv("HUAWEICLOUD_SDK_AK"); demo.sk = System.getenv("HUAWEICLOUD_SDK_SK"); String authExpiration = demo.getFormatExpiration(null, 0); String[] tmpConditions = { "{\"bucket\": \"bucketName\" }", "[\"starts-with\", \"$key\", \"obj\"]" }; String policy = demo.stringToSign(tmpConditions, authExpiration); String policyBase64 = Base64.getEncoder().encodeToString(policy.getBytes(DEFAULT_ENCODING)); String signature = demo.postSignature(policy); // The table carries signed AccessKeyId, policy, and signature. System.out.println("authExpiration=" + authExpiration); System.out.println("policy=" + policy); System.out.println("policyBase64=" + policyBase64); System.out.println("signature=" + signature); // The table carries signed token. System.out.println("token=" + demo.ak + ":" + signature + ":" + policyBase64); } } |
Signature calculation in Python:
# 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" # The default expiration time is 5 minutes. DEFAULT_EXPIRE_SECONDS = 300 GMT_TIMEZONE = "GMT" def __init__(self, ak=None, sk=None): self.ak = ak self.sk = sk # request_date and expires should be set to timestamps, for example, 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): # If binascii or encode("base64") is used, newline characters must be removed. 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() # Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables. # In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and 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) # The table carries signed AccessKeyId, policy, and signature. print("authExpiration=" + auth_expiration) print("policy=" + post_policy) print("policyBase64=" + policy_base64) print("signature=" + signature) # The table carries signed token. print("token=" + demo.ak + ":" + signature + ":" + policy_base64)
Feedback
Was this page helpful?
Provide feedbackThank you very much for your feedback. We will continue working to improve the documentation.See the reply and handling status in My Cloud VOC.
For any further questions, feel free to contact us through the chatbot.
Chatbot