OBS服务应用于互联网数据上传时 使用POST 实现服务端和客户端权限控制和数据上传分离的方法
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<String> conditions = new ArrayList<String>(); //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", ""],)
请求示例:
响应示例:
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 ", ""],)来支持前端上传。
- 点赞
- 收藏
- 关注作者
评论(0)