赞
踩
项目中用到了aws的S3上传,
开发前要先去aws后台,创建用于上传的ak/sk,然后写在代码里(或在系统环境变量里读取),
为了安全起见,这个ak/sk不能暴露在前端,避免被恶意用户获取后随意上传(如果权限设置有问题,还可能随意删除)
所以一般项目都是通过服务端中转一次,即:
前端选择文件、上传到后端、后端再上传到aws。
这个方案最大的弊端,就是中转,链路长了,流量多了,文件如果太大,可能超时,且不好优化。
所以还是希望能由前端直接上传到S3,而不需要中转。
aws提供了这样的解决方案:
1、后端去aws生成一个有时间限制的签名aws域名的url
2、前端通过这个url直接上传aws
这样,ak/sk还是存储在后端,没有了被前端暴露的风险。
下面简述一下使用SpringBoot做后端的步骤:
本文基于SpringBoot2.3.7.RELEASE
1、项目中引用aws-sdk:
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.351</version>
</dependency>
2、创建签名接口:
package beinet.cn.frontstudy.s3; import com.amazonaws.ClientConfiguration; import com.amazonaws.HttpMethod; import com.amazonaws.Protocol; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import org.joda.time.LocalDateTime; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.net.URL; import java.util.Date; @RestController public class S3Controller { // 下面4个参数,是上传到s3的必需配置 private AmazonS3 client; private String accessKey = "我是ak"; private String secretKey = "我是sk"; private String region = "cn-northwest-1";// 对应endpoint参考: https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Beijing.html private String bucket = "我是bucket"; public S3Controller() { ClientConfiguration config = new ClientConfiguration(); config.setProtocol(Protocol.HTTPS); config.disableSocketProxy(); // 初始化S3上传操作类 BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); this.client = AmazonS3ClientBuilder .standard() .withClientConfiguration(config) .withCredentials(new AWSStaticCredentialsProvider(credentials)) .withRegion(region) // Regions.CN_NORTH_1.getName() //.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region)) .enablePathStyleAccess() .build(); } /** * 生成一个预签名的url,给前端js上传 * 注: Method aws官方要求必须是PUT * @param s3FileName 上传到s3的文件相对路径 * @return 签名后的url */ @GetMapping("s3/sign") public String preUploadFile(@RequestParam String s3FileName) { // token设置1小时后过期 Date expiration = LocalDateTime.now().plusHours(1).toDate(); GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(bucket, s3FileName) .withExpiration(expiration) .withMethod(HttpMethod.PUT); URL url = client.generatePresignedUrl(urlRequest); return url.toString(); } }
3、好了,可以写前端代码去测试了:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>aws S3上传演示</title> <script type="text/javascript" src="/res/unpkg/vue.min.js"></script> <script type="text/javascript" src="/res/unpkg/axios.min.js"></script> </head> <body> <hr> <div id="divApp"> <div style="font-weight: bold; font-size: 20px;"> {{title}}</div> <hr> <input type="file" ref="fileInput1" accept="*" @change="getFile"> </div> <hr> <script> var vueApp = new Vue({ el: '#divApp', data: function () { return { title: 'S3免ak/sk上传演示代码', s3signUrl: '', } }, methods: { getS3SignUrl: function () { let url = '/s3/sign?s3FileName=abc/signFile123.xxx'; return axios.get(url).then(response => { this.s3signUrl = response.data; }).catch(error => this.ajaxError(error)); }, // 获取文件数据 getFile: function (event) { this.getS3SignUrl().then(() => { this.uploadToSignUrl(event); }); }, uploadToSignUrl: function (evt) { // 通过s3的签名url,上传到对应的bucket // 注意这里一定必须是file对象,如果使用evt或evt.target也能上传成功,但是S3的文件会多数据 let url = this.s3signUrl; return axios.put(url, evt.target.files[0]).then(response => { alert("上传成功" + response.data); }).catch(error => this.ajaxError(error)); }, ajaxError: function (error) { alert('未知错误' + JSON.stringify(error)); }, }, }); </script> </body> </html>
注:
开发过程中出现的问题:
1、前端上传时报错:from origin has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这是跨域问题,在你的域名下,访问aws的域名,就出现了cors问题,这个需要去aws的S3后台,找到对应的Bucket,在Bucket的【权限】->【跨源资源共享(CORS)】,编辑,输入参考:
[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "PUT", "POST", "GET" ], "AllowedOrigins": [ "*" ], "ExposeHeaders": [ "ETag", "x-amz-meta-custom-header" ] } ]
2、前端上传后,S3上的文件内容不正确:
在前端获取到S3的签名URL之后,只能通过PUT方法,参数必须是javascript里的File对象,否则就会出现错误。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。