
# 使用预签名URL直传OBS（鸿蒙版）
#### 方案架构
应用客户端每个请求都将向应用服务器申请预签名URL，该预签名URL有效期由应用服务器管理。具体流程如[图1]。
图1移动应用访问OBS数据流程   
![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002253300002.png "点击放大")
**角色分析如下：**
- 应用客户端：即最终用户手机上的APP，负责向应用服务器申请包含预签名的URL，以及访问OBS完成数据上传或下载。
- 应用服务器：即提供该Android/iOS**/Harmony**应用的开发者开发的APP后台服务，用于管理凭证信息以及发放预签名URL。
- OBS：即华为云对象存储，负责处理移动应用的数据请求。
**实现流程如下：**
1. 移动应用客户端向应用服务器申请一个预签名的URL。 Android/iOS**/Harmony**应用使用OBS服务时，不需要存储访问密钥（AK/SK）。应用在上传前必须向用户的应用服务器申请访问OBS的URL，并携带必须信息，包括请求类型、资源路径和资源名称。比如上传操作需要标识该URL为上传请求，需要包含上传的路径以及上传对象的名称；下载操作需要标识该URL为下载请求，需要包含所下载对象的名称。
   
2. 应用服务器作为可信设备，在应用服务器上存储访问密钥（AK/SK）。应用服务器在验证客户端身份合法之后，使用应用服务器保存的访问密钥（AK/SK）以及客户端访问的资源、操作类型生成预签名URL。举例：
   ```
   https://examplebucket.obs.cn-north-4.myhuaweicloud.com/objectkey?AccessKeyId=AccessKeyID&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D
   ```
   
3. Android/iOS**/Harmony** 移动应用获取此URL，直接使用该URL操作数据，比如上传或者下载操作。
   URL中会包含用户的AK、签名、有效期、资源等信息，任何拿到这个URL的人均可执行这个操作。OBS服务收到这个请求并验证签名后，认为该请求就是签发URL的用户自己在执行操作。例如构造一个携带签名信息的下载对象URL，拿到相应URL的人能下载这个对象，但该URL只在Expires指定的失效时间内有效（如果使用临时访问密钥，有效期为临时访问密钥有效时长和Expires的最小值）。URL中携带签名主要用于在不提供给其他人SK的情况下，让其他人能用预签发的URL来进行身份认证，并执行预定义的操作。
   
 
#### 资源和成本规划
最佳实践中涉及的资源如下：
表1资源说明 
| 资源                | 资源说明                                                       |
|:---|:---|
| 应用客户端（APP Client） | 最终用户手机上的APP，负责向应用服务器申请包含预签名的URL，以及访问OBS完成数据上传或下载。          |
| 应用服务器（APP Server） | 提供该Android/iOS/Harmony应用的开发者开发的APP后台服务，用于管理凭证信息以及发放预签名URL。 |
| 对象存储服务（OBS）       | 华为云对象存储服务，负责处理移动应用的数据请求。                                   |
   
#### 前提条件
- 创建桶。 在OBS控制台上创建桶。需要将桶权限设置为私有读写或者公共读私有写。
  详细操作步骤请参见[创建桶](https://support.huaweicloud.com/usermanual-obs/zh-cn_topic_0045829088.html)和[配置桶策略](https://support.huaweicloud.com/usermanual-obs/obs_03_0123.html)。
  
- 获取访问密钥（AK/SK）。 预签名URL需要通过访问密钥生成，请参考[访问密钥（AK/SK）](https://support.huaweicloud.com/productdesc-obs/obs_03_0208.html#section0)获取。其中访问密钥（AK/SK）对应的用户需设置所需的最小权限，具体权限设置方法参考[向IAM用户授予OBS资源权限](https://support.huaweicloud.com/usermanual-obs/obs_03_0122.html)。
  
- 创建应用服务器。 此处以ECS为例。请您进入ECS购买页面，根据如下内容进行创建或选择购买ECS实例所需的基础资源。具体操作详情请参见[自定义购买ECS](https://support.huaweicloud.com/usermanual-ecs/ecs_03_7002.html)**。**
  1. **选择计费模式\&区域\&可用区**
     1. 根据业务需求，选择合适的计费模式。本文选择操作相对灵活的按需计费模式。
     
     2. 区域指ECS的物理数据中心所在的位置，建议您就近选择靠近您业务的区域，可减少网络时延，提高访问速度。本文以选择北京四为例。
     
     3. 一个区域内有多个可用区，一个可用区发生故障后不会影响同一区域内下的其它可用区。本文以选择随机分配为例。
     
     
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002290429409.png "点击放大")
     
  
  2. **选择规格\&镜像**
     1. 实例规格本文选择满足测试需求且价格较为实惠的通用入门型t6，2v4G规格。
     
     2. 镜像本文选择公共镜像中的Huawei Cloud EulerOS 2.0 标准版 64位(10GiB)。
     
     
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002290357365.png "点击放大")
     
  
  3. **选择存储**
     本文实现简单应用服务器搭建，只需要选择系统盘存储操作系统。系统盘本文选择通用型SSD，40GiB大小。
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002290357913.png "点击放大")
     
  
  4. **选择网络**
     1. 根据业务需求，选择或创建虚拟私有云，本文选择默认的**vpc-default(192.168.0.0/16)**。
     
     2. 主网卡本文选择default与自动分配IP地址，实际可根据业务需求指定。
     
     
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002293725577.png "点击放大")
     
  
  5. **创建安全组**
     安全组是一种虚拟网络防火墙，能够控制ECS实例的出入流量。创建时，请设置放行SSH（22）、RDP（3389）、HTTP（80）、HTTPS（443）等端口，便于后续访问ECS实例。此处创建的安全组默认设置0.0.0.0/0（表示允许全网段设备访问指定的端口）作为源的规则，后续您可以设置为具体的请求端的IP地址。
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002396448214.png "点击放大")
     
  
  6. **绑定公网IP**
     本实例需要支持公网访问。本文选择直接购买弹性公网IP。
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002294368909.png "点击放大")
     
  
  7. **创建登录凭证**
     创建ECS的登录凭证，以供后续连接ECS实例时使用。
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002260133090.png "点击放大")
     
  
  8. **创建并查看ECS实例**
     确认好配置后，单击右下角"立即购买"，购买成功后可到弹性云服务器列表查看创建好的ECS。
     ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002294480709.png "点击放大")
     
   
 
#### 实施步骤
1. **配置应用服务器。**
   
   1. 获取SDK开发包。 请在各语言的[SDK开发指南](https://support.huaweicloud.com/sdkreference-obs/obs_02_0001.html)中获取。
      
   
   2. 生成预签名URL的代码。 预签名URL的计算方法请参考[URL中携带签名](https://support.huaweicloud.com/api-obs/obs_04_0011.html)。
      下述示例以在应用服务器中使用Java语言开发进行举例。
      ![](https://support.huaweicloud.com/bestpractice-obs/public_sys-resources/note_3.0-zh-cn.png)
      应用服务器需要根据APP操作类型，识别公共请求消息头与自定义请求消息头，并将其加入到预签名URL生成签名计算中。
      - 公共请求消息头，请参考[构造请求](https://support.huaweicloud.com/api-obs/obs_04_0007.html)。
      
      - 自定义请求消息头，请参考对应操作的API文档。例如PUT上传，参考[PUT上传API](https://support.huaweicloud.com/api-obs/obs_04_0080.html)。
       
      此处以Java SDK生成预签名URL的代码为示例：
      ```
      // 本次请求的桶的endpoint
      String endPoint = "http://your-endpoint"; 
      // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险，建议在配置文件或者环境变量中密文存放，使用时解密，确保安全；本示例以ak和sk保存在环境变量中为例，运行本示例前请先在本地环境中设置环境变量ACCESS_KEY_ID和SECRET_ACCESS_KEY_ID。
      // 您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
      String ak = System.getenv("ACCESS_KEY_ID");
      String sk = System.getenv("SECRET_ACCESS_KEY_ID");
      // 创建ObsClient实例 
      ObsClient obsClient = new ObsClient(ak, sk, endPoint); 
      // 替换您的过期时间，单位是秒 
      long expireSeconds = 3600L; 
      // 替换成您对应的操作 
      TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.PUT, expireSeconds); 
      // 替换为请求本次操作访问的桶名和对象名 
      request.setBucketName("bucketname"); 
      request.setObjectKey("objectname"); 
      TemporarySignatureResponse response = obsClient.createTemporarySignature(request); 
      // 成功返回预签名URL，如下打印URL信息 
      System.out.println(response.getSignedUrl());
      ```
      更多相关介绍和示例代码，请参见[使用URL进行授权访问](https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0901.html)。
      
   
   
   
   
2. **移动应用客户端使用获取到的预签名URL发送OBS请求。**
   
   Android应用客户端示例代码：
   ```
   public class Demo extends Activity
   {
       private static String bucketName = "my-obs-bucket-demo";
       private static String objectKey = "my-obs-object-key-demo";
       private static OkHttpClient httpClient;
       private static StringBuffer sb;
       @Override
       protected void onCreate(Bundle savedInstanceState)
       {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
           sb = new StringBuffer();
           /*
           * Constructs a client instance with your account for accessing OBS
           */
           httpClient = new OkHttpClient.Builder().followRedirects(false).retryOnConnectionFailure(false)
                 .cache(null).build();
           final TextView tv = (TextView)findViewById(R.id.tv);
           tv.setText("Click to start test");
           tv.setOnClickListener(new View.OnClickListener()
           {
               @Override
               public void onClick(View v)
               {
                   tv.setClickable(false);
                   AsyncTask<Void, Void, String> task = new DownloadTask();
                   task.execute();
               }
           });
       }
       class DownloadTask extends AsyncTask<Void, Void, String>
       {
           @Override
           protected String doInBackground(Void... params)
           {
               try
               {
                   /*
                    * 这里需要您自己构造上传对象请求到应用服务器来生成到OBS请求的预签名URL
                    * 假如响应结果存放在：response，通过方法获取getSignedUrl()
                    */
                   sb.append("Uploading a new object to OBS from a file\n\n");
                   Request.Builder builder = new Request.Builder();
                   // 使用PUT请求上传对象
                   Request httpRequest = builder.url(response.getSignedUrl()).put(RequestBody.create(MediaType.parse(contentType), "Hello OBS".getBytes("UTF-8"))).build();
                   Call c = httpClient.newCall(httpRequest);
                   Response res = c.execute();
                   sb.append("\tStatus:" + res.code());
                   if (res.body() != null) {
                          sb.append("\tContent:" + res.body().string() + "\n");
                   }
                   res.close();
                   /*
                    * 这里需要您自己构造下载对象请求到应用服务器来生成到OBS请求的预签名URL
                    * 假如响应结果存放在：response，通过方法获取getSignedUrl()
                    */
                   sb.append("Downloading an object\n\n");
                   Request.Builder builder = new Request.Builder();
                   // 使用GET请求下载对象
                   Request httpRequest = builder.url(response.getSignedUrl()).get().build();
                   OkHttpClient httpClient = new OkHttpClient.Builder().followRedirects(false).retryOnConnectionFailure(false).cache(null).build();
                   Call c = httpClient.newCall(httpRequest);
                   Response res = c.execute();
                   System.out.println("\tStatus:" + res.code());
                   if (res.body() != null) {
                          sb.append("\tContent:" + res.body().string() + "\n");
                   }
                   res.close();
                   return sb.toString();
               }
               catch (Exception e)
               {
                   sb.append("\n\n");
                   sb.append(e.getMessage());
                   return sb.toString();
               }
               finally
               {
                   if (httpClient != null)
                   {
                       try
                       {
                           /*
                            * Close obs client
                            */
                           httpClient.close();
                       }
                       catch (IOException e)
                       {
                       }
                   }
               }
           }
           @Override
           protected void onPostExecute(String result)
           {
               TextView tv = (TextView)findViewById(R.id.tv);
               tv.setText(result);
               tv.setOnClickListener(null);
               tv.setMovementMethod(ScrollingMovementMethod.getInstance());
           }
       }
   }
   ```
   Harmony应用客户端示例代码：
   ```
   // 读取文件内容并转为ArrayBuffer格式
   const fileInfo = await fs.open(fileUri, fs.OpenMode.READ_ONLY);
   const fileStat = await fs.stat(fileInfo.fd);
   const data = new ArrayBuffer(fileStat.size);
   await fs.read(fileInfo.fd, data);
   await fs.close(fileInfo.fd);
   try {
     // 这里需要您自己构造上传对象请求到应用服务器来生成到OBS请求的预签名URL
     // 假如响应结果存放在signUrlResult中
     await request(signUrlResult.SignedUrl, {
       method: http.RequestMethod.PUT,
       header: signUrlResult.ActualSignedRequestHeaders,
       extraData: data
     }, 200);
     console.info('putObject success');
   } catch (err) {
     console.info('putObject request error: ' + JSON.stringify(err));
     throw err;
   }
   ```
   
   
 
#### 相关参考
- [Java SDK接口参考文档](https://obssdk.obs.cn-north-1.myhuaweicloud.com/apidoc/cn/java/index.html)
- [Java SDK依赖缺失和依赖冲突的解决方法](https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0303.html)
- Harmony项目完整代码可参考：[obs-harmony-demo](https://github.com/noaccident/obs-harmony-demo)
 
