
# 使用预签名URL直传OBS
#### 方案架构
应用客户端每个请求都将向应用服务器申请预签名URL，该预签名URL有效期由应用服务器管理。具体流程如[图1]。
图1移动应用访问OBS数据流程   
![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000001131063566.png "点击放大")
**角色分析如下：**
- 应用客户端：即最终用户手机上的APP，负责向应用服务器申请包含预签名的URL，以及访问OBS完成数据上传或下载。
- 应用服务器：即提供该Android/iOS应用的开发者开发的APP后台服务，用于管理凭证信息以及发放预签名URL。
- OBS：即华为云对象存储，负责处理移动应用的数据请求。
**实现流程如下：**
1. 移动应用客户端向应用服务器申请一个预签名的URL。 Android和iOS应用使用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移动应用获取此URL，直接使用该URL操作数据，比如上传或者下载操作。 URL中会包含用户的AK、签名、有效期、资源等信息，任何拿到这个URL的人均可执行这个操作。OBS服务收到这个请求并验证签名后，认为该请求就是签发URL的用户自己在执行操作。例如构造一个携带签名信息的下载对象URL，拿到相应URL的人能下载这个对象，但该URL只在Expires指定的失效时间内有效（如果使用临时访问密钥，有效期为临时访问密钥有效时长和Expires的最小值）。URL中携带签名主要用于在不提供给其他人SK的情况下，让其他人能用预签发的URL来进行身份认证，并执行预定义的操作。
   
 
#### 前提条件
- 创建桶。 在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)。
  
 
#### 资源和成本规划
最佳实践中涉及的资源如下：
表1资源说明 
| 资源                | 资源说明                                               |
|:---|:---|
| 应用客户端（APP Client） | 最终用户手机上的APP，负责向应用服务器申请包含预签名的URL，以及访问OBS完成数据上传或下载。  |
| 应用服务器（APP Server） | 提供该Android/iOS应用的开发者开发的APP后台服务，用于管理凭证信息以及发放预签名URL。 |
| 对象存储服务（OBS）       | 华为云对象存储服务，负责处理移动应用的数据请求。                           |
   
#### 实施步骤
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)。
       
      ```
      // 本次请求的桶的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请求。**
   
   ```
   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());
           }
       }
   }
   ```
   
   
 
#### 相关参考
- [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)
 
