服务端签名后直传到OSS
背景信息
和数据直传到 OSS 相比,以上方法有三个缺点:
- 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。
- 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。
- 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。
技术方案
目前通过 Web 前端技术上传文件到 OSS,有三种技术方案:
- 利用OSS Browser.js SDK 将文件上传到 OSS
该方案通过OSS Browser.js SDK直传数据到 OSS,详细的 SDK Demo 请参考上传文件。在网络条件不好的状况下可以通过断点续传的方式上传大文件。该方案在个别浏览器上有兼容性问题,目前兼容 IE10 及以上版本浏览器,主流版本的 Edge、Chrome、Firefox、Safari 浏览器,以及大部分的 Android、iOS、WindowsPhone 手机上的浏览器。更多信息请参见安装 Browser.js SDK。说明 Browser.js SDK 已经支持大部分的 OSS API 接口,包含文件管理、自定义域名设置、图片处理等。
- 使用表单上传方式,将文件上传到 OSS
利用 OSS 提供的 PostObject 接口,使用表单上传方式将文件上传到 OSS。该方案兼容大部分浏览器,但在网络状况不好的时候,如果单个文件上传失败,只能重试上传。操作方法请参见 PostObject 上传方案。说明 关于 PostObject 的详细介绍请参见 PostObject。
- 通过小程序上传文件到 OSS
通过小程序,如微信小程序、支付宝小程序等,利用 OSS 提供的 PostObject 接口来实现表单上传.
本文以Java语言为例,讲解在服务端通过Java代码完成签名,并且设置上传回调,然后通过表单直传数据到OSS。
前提条件
- 应用服务器对应的域名可通过公网访问。
- 确保应用服务器已经安装
Java 1.6
以上版本(执行命令java -version
进行查看)。 - 确保PC端浏览器支持JavaScript。
步骤1:配置应用服务器
步骤2:配置客户端
步骤3:修改CORS
客户端进行表单直传到OSS时,会从浏览器向OSS发送带有Origin
的请求消息。OSS对带有Origin
头的请求消息会进行跨域规则(CORS)的验证。因此需要为Bucket设置跨域规则以支持Post方法。
步骤4:体验上传回调
应用服务器核心代码解析
应用服务器源码包含了签名直传服务和上传回调服务两个功能。
- 签名直传服务
签名直传服务响应客户端发送给应用服务器的GET消息,代码片段如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758protected
void
doGet(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException {
String accessId =
"<yourAccessKeyId>"
;
// 请填写您的AccessKeyId。
String accessKey =
"<yourAccessKeySecret>"
;
// 请填写您的AccessKeySecret。
String endpoint =
"oss-cn-hangzhou.aliyuncs.com"
;
// 请填写您的 endpoint。
String bucket =
"bucket-name"
;
// 请填写您的 bucketname 。
String host =
"https://"
+ bucket +
"."
+ endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
String callbackUrl =
"http://88.88.88.88:8888"
;
String dir =
"user-dir-prefix/"
;
// 用户上传文件时指定的前缀。
// 创建OSSClient实例。
OSS ossClient =
new
OSSClientBuilder().build(endpoint, accessId, accessKey);
try
{
long
expireTime =
30
;
long
expireEndTime = System.currentTimeMillis() + expireTime *
1000
;
Date expiration =
new
Date(expireEndTime);
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
PolicyConditions policyConds =
new
PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,
0
,
1048576000
);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte
[] binaryData = postPolicy.getBytes(
"utf-8"
);
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
Map<String, String> respMap =
new
LinkedHashMap<String, String>();
respMap.put(
"accessid"
, accessId);
respMap.put(
"policy"
, encodedPolicy);
respMap.put(
"signature"
, postSignature);
respMap.put(
"dir"
, dir);
respMap.put(
"host"
, host);
respMap.put(
"expire"
, String.valueOf(expireEndTime /
1000
));
// respMap.put("expire", formatISO8601Date(expiration));
JSONObject jasonCallback =
new
JSONObject();
jasonCallback.put(
"callbackUrl"
, callbackUrl);
jasonCallback.put(
"callbackBody"
,
"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}"
);
jasonCallback.put(
"callbackBodyType"
,
"application/x-www-form-urlencoded"
);
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
respMap.put(
"callback"
, base64CallbackBody);
JSONObject ja1 = JSONObject.fromObject(respMap);
// System.out.println(ja1.toString());
response.setHeader(
"Access-Control-Allow-Origin"
,
"*"
);
response.setHeader(
"Access-Control-Allow-Methods"
,
"GET, POST"
);
response(request, response, ja1.toString());
}
catch
(Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
finally
{
ossClient.shutdown();
}
}
- 上传回调服务
上传回调服务响应OSS发送给应用服务器的POST消息,代码片段如下:
1234567891011121314151617181920212223242526272829303132333435363738394041protected
boolean
VerifyOSSCallbackRequest(HttpServletRequest request, String ossCallbackBody)
throws
NumberFormatException, IOException {
boolean
ret =
false
;
String autorizationInput =
new
String(request.getHeader(
"Authorization"
));
String pubKeyInput = request.getHeader(
"x-oss-pub-key-url"
);
byte
[] authorization = BinaryUtil.fromBase64String(autorizationInput);
byte
[] pubKey = BinaryUtil.fromBase64String(pubKeyInput);
String pubKeyAddr =
new
String(pubKey);
if
(!pubKeyAddr.startsWith(
"https://gosspublic.alicdn.com/"
)
&& !pubKeyAddr.startsWith(
"https://gosspublic.alicdn.com/"
)) {
System.out.println(
"pub key addr must be oss addrss"
);
return
false
;
}
String retString = executeGet(pubKeyAddr);
retString = retString.replace(
"-----BEGIN PUBLIC KEY-----"
,
""
);
retString = retString.replace(
"-----END PUBLIC KEY-----"
,
""
);
String queryString = request.getQueryString();
String uri = request.getRequestURI();
String decodeUri = java.net.URLDecoder.decode(uri,
"UTF-8"
);
String authStr = decodeUri;
if
(queryString !=
null
&& !queryString.equals(
""
)) {
authStr +=
"?"
+ queryString;
}
authStr +=
"\n"
+ ossCallbackBody;
ret = doCheck(authStr, authorization, retString);
return
ret;
}
protected
void
doPost(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException {
String ossCallbackBody = GetPostBody(request.getInputStream(),
Integer.parseInt(request.getHeader(
"content-length"
)));
boolean
ret = VerifyOSSCallbackRequest(request, ossCallbackBody);
System.out.println(
"verify result : "
+ ret);
// System.out.println("OSS Callback Body:" + ossCallbackBody);
if
(ret) {
response(request, response,
"{\"Status\":\"OK\"}"
, HttpServletResponse.SC_OK);
}
else
{
response(request, response,
"{\"Status\":\"verify not ok\"}"
, HttpServletResponse.SC_BAD_REQUEST);
}
}
本地代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏