OBS服务应用于互联网数据上传时 使用POST 实现服务端和客户端权限控制和数据上传分离的方法

网友投稿 828 2022-05-30

1      背景

采用JavaScript SDK 等客户端直接签名时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OBS提供了服务端签名后直传的方案 解决此问题。问题代码如下所示(需要在前端使用ak sk作为初始化条件):

2      原理介绍

1.     客户端在登陆后,向app server请求上传对象的鉴权token;

2.     App server 根据永久AK SK和针对上传对象和桶的policy生成一个token(具体参考后的代码示例)

3.     前端组件 收到token后使用post请求将token作为一个表单项进行对象上传。

同时我们也做了示例网站进行功能的展示;https://codepen.io/x00403408/pen/xQYZgE 此地址示例如何生成一个token;

https://codepen.io/x00403408/pen/WYMrbY 网站示例POST请求如何使用token进行数据的上传

2.1.2        约束限制

1.     Post表单上传是单流上传,没法实现断点续传功能。因此比较适合一些小文件的上传。

2.     Post上传对于表单域我们采用强校验模式,只要携带的表单域除我们没定义的外都要包含在policy中参与签名计算。具体参考文档描述:https://support.huaweicloud.com/api-obs/zh-cn_topic_0106557184.html

3      流程和源码解析

3.1      服务端代码

Java SDK 代码示例生成服务端token

package samples_java;   import java.io.File; import java.io.IOException; import java.util.ArrayList;   import com.obs.services.ObsClient; import com.obs.services.ObsConfiguration; import com.obs.services.exception.ObsException; import com.obs.services.model.AuthTypeEnum; import com.obs.services.model.PostSignatureRequest; import com.obs.services.model.PostSignatureResponse;   public class TestPostObject {     private static final String endPoint = "obs.myhwclouds.com";       private static final String ak = "";       private static final String sk = "";       private static ObsClient obsClient;       private static String bucketName = "";       private static AuthTypeEnum authType = AuthTypeEnum.OBS;       public static void main(String[] args) throws IOException {         ObsConfiguration config = new ObsConfiguration();         config.setEndPoint(endPoint);         config.setAuthType(authType);           try {             obsClient = new ObsClient(ak, sk, config);             //创建token             PostSignatureRequest request = new PostSignatureRequest();             request.setExpires(3600);             ArrayList conditions = new ArrayList();             //Condition可以根据不同业务头域的需求进行增删             conditions.add("[\"starts-with\",\"$content-type\",\"\"]");             conditions.add("[\"starts-with\",\"$key\",\"\"]");             conditions.add("{\"bucket\":\""+bucket+"\"}");             request.setConditions(conditions);             PostSignatureResponse response = obsClient.createPostSignature(request);             String Token = response.getToken();                     } catch (Exception ex) {             if (ex instanceof ObsException) {                 ObsException e = (ObsException) ex;                 System.out.println("Message: " + e.getMessage());             } else {                 ex.printStackTrace();             }         } finally {             if (obsClient != null) {                 try {                     obsClient.close();                 } catch (IOException e) {                 }             }         }     }   }

3.2      客户端代码

客户端的代码在使用POST进行表单上传时候直接构造POST请求,其中传递token等表单域信息即可(JS代码);此处可以参考我们的web示例(https://codepen.io/x00403408/pen/WYMrbY)的代码(截取部分关键代码):

$(window.document).ready(function() {     $('#progressBar').hide();   $('#upload').click(function() {     var token = $.trim($('#token').val());     var bucket = $.trim($('#bucket').val());     var endpoint = $.trim($('#endpoint').val());       var fileList = $('#inputFile')[0].files;     if(token === '' || bucket === '' || endpoint === '' || fileList.length <= 0){       window.alert('输入有误!');       return;     }       if(isIpAddress(endpoint)){       endpoint += '/' + bucket;     }else{       endpoint = bucket + '.' + endpoint;     }     endpoint = 'https://' + endpoint;         var xhr = new XMLHttpRequest();     xhr.open('POST', endpoint, true);     var formData = new FormData();     var key = $.trim($('#key').val()) || fileList[0].name;     formData.append('key', key);     formData.append('token', token);     var contentType = mimeTypes[key.substring(key.lastIndexOf('.') + 1)];     //if(contentType){            //formData.append('content-type', contentType);     //}     //var tokens = token.split(':');     //formData.append('AccessKeyId', tokens[0]);     //formData.append('Signature', tokens[1]);     //formData.append('Policy', tokens[2]);     formData.append('file', fileList[0]);         var start = new Date().getTime();     var cost;       xhr.upload.addEventListener('progress', function(event) {       if (event.lengthComputable) {         if(!cost){           $('#progressBar').show();         }         cost = new Date().getTime() - start;         var uploadSpeed = (event.loaded / 1024 / cost * 1000).toFixed(2) + 'KB\/s';         var percentage = Math.round(event.loaded * 100 / event.total) + '%';         $('#uploadSpeed').html(uploadSpeed);         $('#percentage').html(percentage);         $('#progressBar').css('width', percentage);       }     }, false);       xhr.onreadystatechange = function(response) {       if(xhr.readyState === 4){         if(xhr.status < 300){           console.log('上传成功!');         }else{           console.log('上传失败!');         }        }     };     xhr.send(formData);

3.3      过程问题说明

3.3.1        POST 上传对应响应码200或204对兼容性影响说明

POST 上传默认的响应码是204,但是在IE8/9浏览器对此响应码是不做状态上报的,导致web中的JS代码无法获取什么时候上传成功。际上是可以通过 success_action_status 重新定义响应状态码,在Post请求时候需要携带此项到表单域中。(POLICY中:["starts-with", "$success_action_status", ""],)

请求示例:

OBS服务应用于互联网数据上传时 使用POST 实现服务端和客户端权限控制和数据上传分离的方法

响应示例:

3.3.2        文件的content-type设置

因为 在web/app类应用中为了防止不同用户上传的图片产生重名问题,客户在上传时候会针对原始文件名称生成一个唯一的对象名称KEY值,这样防止了存储在对象存储时候对象名称相同时候覆盖。

而这个时候导致一个问题,存储在对象存储的文件缺失了后缀名,JS-sdk不会增加content-type描述(通过后缀名增加)。OBS对于POST中携带默认的content-type描述不识别,导致设置为application/octect-stream;使得在浏览器下载时候不能在线展示;

那么这个时候处理需要在前端上传时候增加content-type表单域;同时在后端生成上传token时policy也需要增加(POLICY中:["starts-with", "$ content-type ", ""],)来支持前端上传。

OBS

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:UWP 手绘视频创作工具技术分享系列 - Ink & Surface Dial
下一篇:Node.js简要小总结
相关文章