使用预签名URL直传OBS(鸿蒙版)
方案架构
应用客户端每个请求都将向应用服务器申请预签名URL,该预签名URL有效期由应用服务器管理。具体流程如图1。
角色分析如下:
- 应用客户端:即最终用户手机上的APP,负责向应用服务器申请包含预签名的URL,以及访问OBS完成数据上传或下载。
- 应用服务器:即提供该Android/iOS/Harmony应用的开发者开发的APP后台服务,用于管理凭证信息以及发放预签名URL。
- OBS:即华为云对象存储,负责处理移动应用的数据请求。
- 移动应用客户端向应用服务器申请一个预签名的URL。
Android/iOS/Harmony应用使用OBS服务时,不需要存储访问密钥(AK/SK)。应用在上传前必须向用户的应用服务器申请访问OBS的URL,并携带必须信息,包括请求类型、资源路径和资源名称。比如上传操作需要标识该URL为上传请求,需要包含上传的路径以及上传对象的名称;下载操作需要标识该URL为下载请求,需要包含所下载对象的名称。
- 应用服务器作为可信设备,在应用服务器上存储访问密钥(AK/SK)。应用服务器在验证客户端身份合法之后,使用应用服务器保存的访问密钥(AK/SK)以及客户端访问的资源、操作类型生成预签名URL。举例:
https://examplebucket.obs.cn-north-4.myhuaweicloud.com/objectkey?AccessKeyId=AccessKeyID&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D
- Android/iOS/Harmony移动应用获取此URL,直接使用该URL操作数据,比如上传或者下载操作。
URL中会包含用户的AK、签名、有效期、资源等信息,任何拿到这个URL的人均可执行这个操作。OBS服务收到这个请求并验证签名后,认为该请求就是签发URL的用户自己在执行操作。例如构造一个携带签名信息的下载对象URL,拿到相应URL的人能下载这个对象,但该URL只在Expires指定的失效时间内有效(如果使用临时访问密钥,有效期为临时访问密钥有效时长和Expires的最小值)。URL中携带签名主要用于在不提供给其他人SK的情况下,让其他人能用预签发的URL来进行身份认证,并执行预定义的操作。
资源和成本规划
最佳实践中涉及的资源如下:
资源 |
资源说明 |
---|---|
应用客户端(APP Client) |
最终用户手机上的APP,负责向应用服务器申请包含预签名的URL,以及访问OBS完成数据上传或下载。 |
应用服务器(APP Server) |
提供该Android/iOS/Harmony应用的开发者开发的APP后台服务,用于管理凭证信息以及发放预签名URL。 |
对象存储服务(OBS) |
华为云对象存储服务,负责处理移动应用的数据请求。 |
前提条件
- 创建桶。
- 获取访问密钥(AK/SK)。
预签名URL需要通过访问密钥生成,请参考访问密钥(AK/SK)获取。其中访问密钥(AK/SK)对应的用户需设置所需的最小权限,具体权限设置方法参考向IAM用户授予OBS资源权限。
- 创建应用服务器。
此处以ECS为例。请您进入ECS购买页面,根据如下内容进行创建或选择购买ECS实例所需的基础资源。具体操作详情请参见自定义购买ECS。
- 选择计费模式&区域&可用区
- 根据业务需求,选择合适的计费模式。本文选择操作相对灵活的按需计费模式。
- 区域指ECS的物理数据中心所在的位置,建议您就近选择靠近您业务的区域,可减少网络时延,提高访问速度。本文以选择北京四为例。
- 一个区域内有多个可用区,一个可用区发生故障后不会影响同一区域内下的其它可用区。本文以选择随机分配为例。
- 选择规格&镜像
- 实例规格本文选择满足测试需求且价格较为实惠的通用入门型t6,2v4G规格。
- 镜像本文选择公共镜像中的Huawei Cloud EulerOS 2.0 标准版 64位(10GiB)。
- 选择存储
本文实现简单应用服务器搭建,只需要选择系统盘存储操作系统。系统盘本文选择通用型SSD,40GiB大小。
- 选择网络
- 根据业务需求,选择或创建虚拟私有云,本文选择默认的vpc-default(192.168.0.0/16)。
- 主网卡本文选择default与自动分配IP地址,实际可根据业务需求指定。
- 创建安全组
安全组是一种虚拟网络防火墙,能够控制ECS实例的出入流量。创建时,请设置放行SSH(22)、RDP(3389)、HTTP(80)、HTTPS(443)等端口,便于后续访问ECS实例。此处创建的安全组默认设置0.0.0.0/0(表示允许全网段设备访问指定的端口)作为源的规则,后续您可以设置为具体的请求端的IP地址。
- 绑定公网IP
本实例需要支持公网访问。本文选择直接购买弹性公网IP。
- 创建登录凭证
创建ECS的登录凭证,以供后续连接ECS实例时使用。
- 创建并查看ECS实例
确认好配置后,单击右下角立即购买,购买成功后可到弹性云服务器列表查看创建好的ECS。
- 选择计费模式&区域&可用区
实施步骤
- 配置应用服务器。
- 获取SDK开发包。
请在各语言的SDK开发指南中获取。
- 生成预签名URL的代码。
预签名URL的计算方法请参考URL中携带签名。
下述示例以在应用服务器中使用Java语言开发进行举例。
此处以Java SDK生成预签名URL的代码为示例:
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进行授权访问。
- 获取SDK开发包。
- 移动应用客户端使用获取到的预签名URL发送OBS请求。
Android应用客户端示例代码:
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()); } } }
Harmony应用客户端示例代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// 读取文件内容并转为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接口参考文档
- Java SDK依赖缺失和依赖冲突的解决方法
- Harmony项目完整代码可参考:obs-harmony-demo