更新时间:2024-09-18 GMT+08:00
分享

使用预签名URL直传OBS

方案架构

应用客户端每个请求都将向应用服务器申请预签名URL,该预签名URL有效期由应用服务器管理。具体流程如图1

图1 移动应用访问OBS数据流程

角色分析如下:

  • 应用客户端:即最终用户手机上的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控制台上创建桶。需要将桶权限设置为私有读写或者公共读私有写。

    详细操作步骤请参见创建桶配置桶策略

  • 获取访问密钥(AK/SK)。

    预签名URL需要通过访问密钥生成,请参考访问密钥(AK/SK)获取。其中访问密钥(AK/SK)对应的用户需设置所需的最小权限,具体权限设置方法参考向IAM用户授予OBS资源权限

资源和成本规划

最佳实践中涉及的资源如下:

表1 资源说明

资源

资源说明

应用客户端(APP Client)

最终用户手机上的APP,负责向应用服务器申请包含预签名的URL,以及访问OBS完成数据上传或下载。

应用服务器(APP Server)

提供该Android/iOS应用的开发者开发的APP后台服务,用于管理凭证信息以及发放预签名URL。

对象存储服务(OBS)

华为云对象存储服务,负责处理移动应用的数据请求。

实施步骤

  1. 配置应用服务器。

    1. 获取SDK开发包。

      请在各语言的SDK开发指南中获取。

    2. 生成预签名URL的代码。

      预签名URL的计算方法请参考URL中携带签名

      下述示例以在应用服务器中使用Java语言开发进行举例。

      应用服务器需要根据APP操作类型,识别公共请求消息头与自定义请求消息头,并将其加入到预签名URL生成签名计算中。

      • 公共请求消息头,请参考构造请求
      • 自定义请求消息头,请参考对应操作的API文档。例如PUT上传,参考PUT上传API
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      // 本次请求的桶的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进行授权访问

  2. 移动应用客户端使用获取到的预签名URL发送OBS请求。

      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
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    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());
            }
        }
    }
    

相关文档