All Products
Search
Document Center

Object Storage Service:Obtain signature information from the application server and upload data to OSS

Last Updated:Sep 03, 2024

In scenarios in which you want to restrict the attributes of an object to upload, obtain necessary information, such as a signature and an upload policy from the application server. This way, the client can directly upload an object to Object Storage Service (OSS) without using OSS SDKs. The server-generated policy can impose limits on the attributes of objects that you want to upload to OSS, such as the object size and type. This method is suitable for scenarios in which you want to upload objects by using HTML forms, but you cannot use this method for multipart upload or resumable upload.

How it works

The following figure describes how to integrate Security Token Service (STS) with OSS to obtain signature information from the application server and upload data to OSS. After STS is integrated, the client can use the security token obtained from STS to perform form upload without directly using long-term access credentials. This way, the security of the upload process is enhanced.

image
  1. The client sends a request to the application server to obtain an upload policy.

    The client first sends a request to the application server, which includes the minimum required permissions for the upload operation, such as permissions to upload to a specific bucket, and the desired validity period. Compared with directly requesting a signature and a POST policy, the client requests a signature and an upload policy which is generated by using a security token with specific permissions.

  2. The application server sends a request to obtain a security token from STS.

    After receiving the request from the client, the application server uses the long-term credentials to initiate a request to STS to generate a security token with limited permissions and a specific validity period.

  3. STS returns a security token to the application server.

  4. The application server generates an upload policy based on the security token and other upload limits.

    The application server generates a secure upload policy based on the security token and other upload limits, such as the bucket name, directory path, and validity period, and returns the policy to the client.

  5. The client creates and submits a form upload request.

    The client uses the security token and upload policy to create an HTML form based on the object information. In this case, the signature and security token added to the upload policy allow the client to directly interact with OSS without exposing the long-term credentials of the application server.

  6. OSS returns a success response to the client.

Prerequisites

  • A bucket is created. For more information, see Create a bucket.

  • A cross-origin resource sharing (CORS) rule is configured for the bucket and POST is selected for the Allow Methods parameter in the CORS rule. For more information, see CORS.

Required permissions

To upload data to OSS, you must have the oss:PutObject permission. For more information, see Attach a custom policy to a RAM user.

Deployment

Sample project deployed in this scenario: server-signed-direct-upload.zip

Quick deployment

You can use Resource Orchestration Service (ROS) to configure an Elastic Compute Service (ECS) instance and an OSS bucket, and deploy an application server and a client on the ECS instance. To use ROS to obtain signature information from the application server and upload data to OSS, perform the following steps:

  1. Deploy cloud resources with a few clicks.

    1. Use Create Stack wizard in the ROS console.

    2. In the Configure Parameters step of the Create Stack wizard in the ROS console, enter a stack name and the name of the OSS bucket that you want to create. Specify the zone, instance type, system disk category, and password for the ECS instance that you want to purchase, and then click Next. In the Check and Confirm step, confirm your settings and click Create.

      On the Stack Information tab of the page that appears, the status of the stack is Creating.

    3. After the stack enters the Created state, click the Outputs tab to view the created ECS instance and OSS bucket.

  2. Obtain signature information from the application server and upload data to OSS.

    1. On the Outputs tab, copy the value of OssClientAddress and open it in your browser.

    2. On the Transfer Data from Web Client to OSS page, click Select File, select a file of a specific type, and then click Upload.

  3. Release the resources to avoid unnecessary charges.

    1. In the upper-right corner of the stack page, click Delete.

    2. In the Delete Stack dialog box, set Method to Delete the Stack to Release Resources and click OK.

Manual deployment

Preparations

  1. Create a RAM user.

    1. Log on to the RAM console.

    2. In the left-side navigation pane, choose Identities > Users.

    3. On the Users page, click Create User.

    4. Configure the Logon Name and Display Name parameters.

    5. In the Access Mode section, select OpenAPI Access and click OK.

    6. Follow the on-screen instructions to complete security verification.

    7. Copy the AccessKey pair (AccessKey ID and AccessKey secret).

      Important

      You can obtain the AccessKey secret of a RAM user only when you create the RAM user. You must keep the AccessKey secret secure to prevent credential leaks.

  2. Grant the RAM user the permissions to call the AssumeRole operation.

    After you create the RAM user, grant the RAM user the permissions to call the AssumeRole operation of STS.

    1. On the Users page, find the RAM user that you created and click Add Permissions in the Actions column.

    2. In the Policy section of the Add Permissions panel, select the AliyunSTSAssumeRoleAccess policy.

      Note

      The AliyunSTSAssumeRoleAccess policy allows a RAM user to call the AssumeRole operation. The permissions of the policy are independent of the permissions required to allow the RAM user to obtain temporary access credentials from STS and initiate requests to OSS.

      image.png

    3. Click Grant permissions.

  3. Create a RAM role.

    Create a RAM role and specify the permissions of the RAM role when the RAM role is assumed.

    1. In the left-side navigation pane, choose Identities > Roles.

    2. Click Create Role. In the Select Role Type step of the Create Role wizard, set Select Trusted Entity to Alibaba Cloud Account and click Next.

    3. In the Configure Role step of the Create Role wizard, set RAM Role Name to RamOssTest and Select Trusted Alibaba Cloud Account to Current Alibaba Cloud Account.

    4. Click OK. After you create the RAM role, click Close.

    5. On the Roles page, enter RamOssTest in the search box, click the search icon, and then click RamOssTest in the search result.

    6. Click Copy on the right side of the RamOssTest page to save the Alibaba Cloud Resource Name (ARN) of the RAM role.arn

  4. Grant the RAM role the permissions to upload objects to OSS.

    Attach a policy to the RAM role to grant the RAM role the permissions to access OSS resources when the RAM role is assumed. In this example, if you want a RAM user to assume the RAM role and upload objects only to a specific bucket, you must attach a policy that allows the RAM role to upload objects to the bucket.

    1. Create a custom policy to grant the RAM role the permissions to upload objects to OSS.

      1. In the left-side navigation pane, choose Permissions > Policies.

      2. On the Policies page, click Create Policy.

      3. On the Create Policy page, click JSON. Enter the following script in the code editor to grant the RAM role the permissions to upload objects to the examplebucket bucket.

        {
            "Version": "1",
            "Statement": [
             {
                   "Effect": "Allow",
                   "Action": [
                     "oss:PutObject"
                   ],
                   "Resource": [
                     "acs:oss:*:*:examplebucket/*"             
                   ]
             }
            ]
        }
      4. Click Next to edit policy information.

      5. In the Basic information section, set Name to RamTestPolicy and click OK.

    2. Attach the custom policy to the RamOssTest role.

      1. In the left-side navigation pane, choose Identities > Roles.

      2. On the Roles page, find the RamOssTest role.

      3. Click Grant Permission in the Actions column of the RamOssTest role.

      4. In the Grant Permission panel, select Custom Policy from the drop-down list in the Policy section and then select the RamTestPolicy policy.

      5. Click Grant permissions.

Procedure

  1. Modify the config.js configuration file in the server directory of the sample project source code.

    module.exports = {
      // Obtain the AccessKey pair of the RAM user and the ARN of the RAM role from environment variables.
      accessKeyId: process.env.OSS_ACCESS_KEY_ID,
      accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
      roleArn: process.env.OSS_STS_ROLE_ARN,
      // Specify the region in which the bucket is located. For example, if the bucket is located in the China (Hangzhou) region, set the region to oss-cn-hangzhou. 
      region: "oss-cn-hangzhou",
      // Specify the name of the bucket. 
      bucket: "examplebucket",
    };
  2. Generate an upload policy based on the upload limits and the security token requested by the application server from STS.

    const { exec } = require("child_process");
    const path = require("path");
    const express = require("express");
    const cors = require("cors");
    
    const moment = require("moment");
    
    const OSS = require("ali-oss");
    const { STS } = require("ali-oss");
    
    const config = require("./config");
    
    const getToken = async () => {
      const { accessKeyId, accessKeySecret, roleArn, bucket } = config;
      const seconds=3000; // Set the validity period of the security token to 3,000 seconds. 
      const date = new Date();
      date.setSeconds(date.getSeconds() + seconds);
      const dir = "user-dirs/";
      const policy = {
        expiration: date.toISOString(), // Specify the validity period of the request. 
        conditions: [
          ["content-length-range", 0, 1048576000] // Specify the size limit for the object that you want to upload. 
          ["starts-with", "$key", dir], // Specify that objects can be uploaded only to the user-dirs directory. 
          {bucket }, // Specify that objects can be uploaded only to a specific bucket. 
        ],
      };
      /* Use stsToken to upload the object. */
      let stsToken;
      if (roleArn) {
        let sts = new STS({
          accessKeyId,
          accessKeySecret,
        });
        const {
          credentials: { AccessKeyId, AccessKeySecret, SecurityToken },
        } = await sts.assumeRole(roleArn, "", seconds, "sessiontest");
        stsToken = SecurityToken;
        client = new OSS({
          accessKeyId: AccessKeyId,
          accessKeySecret: AccessKeySecret,
          stsToken,
        });
      }
    
      // Generate a signature. 
      const formData = await client.calculatePostSignature(policy);
    
      // Specify the parameters in the response. 
      const params = {
        expire: moment(date).unix().toString(),
        policy: formData.policy,
        signature: formData.Signature,
        accessid: formData.OSSAccessKeyId,
        stsToken,
        host: `http://${config.bucket}.${config.region}.aliyuncs.com`,
        dir,
      };
    
      return params;
    };
    
    const app = express();
    app.use(cors());
    // Generate a security token based on your business requirements. Each time you call the /token operation, the getToken function is called to generate a new security token. 
    // In the example, the security token can be reused within the validity period of 3,000 seconds. After the validity period ends, a new security token is generated when you call the /token operation again. 
    app.get("/token", async (req, res) => {
      const result = await getToken();
      res.header["Access-Control-Allow-Origin"] = "*";
      res.json(result);
    });
    
    app.get(/^(.+)*\.(html|js|ico)$/i, async (req, res) => {
      const pat = path.join(__dirname, "../", req.originalUrl);
      res.sendFile(pat);
    });
    
    const url = "http://127.0.0.1:3001/index.html";
    app.listen(3001, () => console.log("Open:" + url));
    
    if (process.platform === "win32") {
      exec('start ${url}'); // Apply to the Windows operating system.
    } else if (process.platform === "darwin") {
      exec('open ${url}'); // Apply to the macOS operating system.
    } else {
      exec('xdg-open ${url}); // Apply to the Linux operating system.
    }
    
  3. Receive the security token and the upload policy returned to the client from the application server.

    {
        "expire": "1716879673",
        "policy": "eyJl****",
        "signature": "YGTr****",
        "accessid": "STS.NULw****",
        "stsToken": "CAIS****",
        "host": "http://examplebucket.oss-cn-hangzhou.aliyuncs.com",
        "dir": "user-dirs/"
    }

    The following table describes the fields that are included in the message body.

    Field

    Description

    expire

    The expiration time of the upload policy specified by the application server. The timestamp follows the UNIX time format. It is the number of seconds that have elapsed since 00:00:00 on January 1, 1970.

    policy

    The policy for form upload. The policy is a Base64-encoded string. For more information, see POST policy.

    signature

    The signature string of the policy. For more information, see POST signature.

    accessid

    The AccessKey ID of the temporary AccessKey pair.

    stsToken

    The security token that is obtained from STS.

    host

    The domain name of the bucket.

    dir

    The prefix contained in the names of the objects that you want to upload.

  4. Create and submit a form upload request on the client.

    Note
    • Except for the file form field, the size of each form field (including key) cannot exceed 8 KB.

    • By default, an existing object that has the same name as the object that you want to upload is overwritten. If you do not want to overwrite the existing object, include the x-oss-forbid-overwrite header in the upload request and set the x-oss-forbid-overwrite header to true. This way, if you upload an object whose name is the same as an existing object, the upload fails and OSS returns the FileAlreadyExists error code.

    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
        <title>Upload data to OSS from web clients</title>
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
        />
      </head>
      <body>
        <h2>Upload data to OSS from web clients --- Add signatures to requests by using OSS SDK for Node.js on the application server</h2>
        <ol>
          <li>Configure a CORS rule for the bucket and select POST for Allow Methods in the rule. Otherwise, you cannot perform form upload. </li>
        </ol>
        <br />
        <div>
          <input type="file" id="fileInput" name="fileInput" />
          <input type="button" value="Upload" onclick="upload()" />
        </div>
        <script>
          function upload() {
            const tokenUrl = "http://127.0.0.1:3001/token";
            fetch(tokenUrl).then(async (res) => {
              const { policy, signature, accessid, host, dir, stsToken } =
                await res.json();
    
              let formData = new FormData();
              formData.append("success_action_status", "200"); // Specify that the application server returns HTTP status code 200 when the upload task succeeds. By default, HTTP status code 204 is returned. 
              formData.append("policy", policy);
              formData.append("signature", signature);
              formData.append("OSSAccessKeyId", accessid);
              if (stsToken) formData.append("x-oss-security-token", stsToken);
    
              const files = document.getElementById("fileInput").files;
              if (files.length === 0) {
                alert("Select an object");
                return;
              }
              formData.append("key", dir + files[0].name); // Specify the name of the object.
              formData.append("file", files[0]); // Specify that file must be the last form field.
    
              const param = {
                method: "POST",
                body: formData,
              };
              fetch(host, param)
                .then((data) => {
                  console.log(data);
                  alert("Uploaded");
                })
                .catch((error) => {
                  console.error("Error:", error);
                });
            });
          }
        </script>
      </body>
    </html>
    

References

In most cases, the application server needs to be informed of the information about uploaded objects, such as the names of the uploaded objects. If you upload an image, the application server needs to be informed of the image size. You can configure upload callbacks to meet the preceding requirements. For more information, see Overview.