OBS服务应用于互联网数据上传时 使用POST 实现服务端和客户端权限控制和数据上传分离的方法
【摘要】 1 背景采用JavaScript SDK 等客户端直接签名时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OBS提供了服务端签名后直传的方案 解决此问题。问题代码如下所示(需要在前端使用ak sk作为初始化条件):2 原理介绍1. 客户端在登陆后,向app server请求上传对象的鉴权token;2. ...
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 ", ""],)来支持前端上传。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
评论(0)