目录
1、导论
2、客户端直传
3、创建RAM用户以及RAM角色
4、如何实现客户端直传
4.1、跨域访问
4.2、安全授权
5、代码示例
5.1、后端代码实例
5.2、客户端代码实例
1、导论
最近在做项目的过程中使用到了阿里云OSS来存储客户端上传的文件,方法是直接将客户端上传的文件发送到业务服务器,再通过业务服务器将文件上传到OSS对象存储服务器当中。
起初觉得是什么问题的,但是在使用的过程中会发现上传文件的速度很慢,显然是受到业务服务器宽带的限制。为了解决这个问题我翻看了OSS的官方文档,在里面学到了不经过业务服务器的转发,而是直接让客户端和OSS服务器进行连接直接上传。下面为大家讲解一下在客户端直连OSS的操作过程。
2、客户端直传
客户端直传是指客户端直接上传文件到对象存储OSS。相对于服务端代理上传,客户端直传避免了业务服务器中转文件,提高了上传速度,节省了服务器资源。
在典型的服务端和客户端架构下,常见的文件上传方式是服务端代理上传:客户端将文件上传到业务服务器,然后业务服务器将文件上传到OSS。在这个过程中,一份数据需要在网络上传输两次,会造成网络资源的浪费,增加服务端的资源开销。为了解决这一问题,可以在客户端直连OSS来完成文件上传,无需经过业务服务器中转。
总结:
服务器代理上传和客户端直传相比,有三个缺点:
- 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。
- 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。
- 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。
3、创建RAM用户以及RAM角色
在RAM访问控制工作台中,在左侧导航栏中选择“用户”,创建用户并勾选OpenAPI调用访问
创建完毕后为该用户添加授权,搜索oss以及sts将管理oss的权限以及sts权限授权给创建的用户,此时使用该用户的AccessKeyId以及AccessKeySecret凭证就可以向STS获取临时凭证。
再通过左侧导航栏的角色,创建一个普通用户,并授权oss权限。
最后查看角色详情,可以看到角色的ARN,即 roleArn。
4、如何实现客户端直传
实现客户端直传需要解决以下两个大问题:
4.1、跨域访问
如果客户端是Web端或小程序,需要解决跨域访问被限制的问题。浏览器以及小程序容器出于安全考虑,通常都会限制跨域访问,这一限制也会限制客户端代码直连OSS。
4.2、安全授权
而实现客户端直传的另外一个大问题就是安全授权问题。
客户端要想实现上传,必须要得到AccessKeyId以及AccessKeySecret凭证,通过凭证才能向OSS发起请求。而如果直接将这两个身份凭证放到前端,用户很容易就能够通过F12获取到,一旦被获取,用户可以用来做任何事情,例如向OSS中存储大量垃圾数据。
为了解决这一问题,OSS提供了STS临时访问凭证。在服务端使用STS SDK获取STS临时访问凭证,然后在客户端使用STS临时凭证和OSS SDK直接上传文件。客户端能重复使用服务端生成的STS临时访问凭证生成签名。
客户端向业务服务器请求临时访问凭证。
业务服务器使用STS SDK调用AssumeRole接口,获取临时访问凭证。
STS生成并返回临时访问凭证给业务服务器。
业务服务器返回临时访问凭证给客户端。
客户端使用OSS SDK通过该临时访问凭证上传文件到OSS。
OSS返回成功响应给客户端。
5、代码示例
5.1、后端代码实例
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
public class StsServiceSample {public static void main(String[] args) { // STS服务接入点,例如sts.cn-hangzhou.aliyuncs.com。 String endpoint = "sts.cn-hangzhou.aliyuncs.com";String accessKeyId = "OSS_ACCESS_KEY_ID";String accessKeySecret = "OSS_ACCESS_KEY_SECRET";String roleArn = "OSS_STS_ROLE_ARN";// 自定义角色会话名称,用来区分不同的令牌,例如可填写为SessionTest。 String roleSessionName = "yourRoleSessionName";// 如果policy为空,则临时访问凭证将获得角色拥有的所有权限。 String policy = "{\n" +" \"Version\": \"1\", \n" +" \"Statement\": [\n" +" {\n" +" \"Action\": [\n" +" \"oss:PutObject\"\n" +" ], \n" +" \"Resource\": [\n" +" \"acs:oss:*:*:*\" \n" +" ], \n" +" \"Effect\": \"Allow\"\n" +" }\n" +" ]\n" +"}";// 临时访问凭证的有效时间,单位为秒。最小值为900,最大值以当前角色设定的最大会话时间为准。当前角色最大会话时间取值范围为3600秒~43200秒,默认值为3600秒。// 在上传大文件或者其他较耗时的使用场景中,建议合理设置临时访问凭证的有效时间,确保在完成目标任务前无需反复调用STS服务以获取临时访问凭证。Long durationSeconds = 3600L;try {// regionId表示RAM的地域ID。以华东1(杭州)地域为例,regionID填写为cn-hangzhou。也可以保留默认值,默认值为空字符串("")。String regionId = "";// 添加endpoint。适用于Java SDK 3.12.0及以上版本。DefaultProfile.addEndpoint(regionId, "Sts", endpoint);// 添加endpoint。适用于Java SDK 3.12.0以下版本。// DefaultProfile.addEndpoint("",regionId, "Sts", endpoint);// 构造default profile。IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);// 构造client。DefaultAcsClient client = new DefaultAcsClient(profile);final AssumeRoleRequest request = new AssumeRoleRequest();// 适用于Java SDK 3.12.0及以上版本。request.setSysMethod(MethodType.POST);// 适用于Java SDK 3.12.0以下版本。//request.setMethod(MethodType.POST);request.setRoleArn(roleArn);request.setRoleSessionName(roleSessionName);request.setPolicy(policy); request.setDurationSeconds(durationSeconds); final AssumeRoleResponse response = client.getAcsResponse(request);System.out.println("Expiration: " + response.getCredentials().getExpiration());System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId());System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret());System.out.println("Security Token: " + response.getCredentials().getSecurityToken());System.out.println("RequestId: " + response.getRequestId());} catch (ClientException e) {System.out.println("Failed:");System.out.println("Error code: " + e.getErrCode());System.out.println("Error message: " + e.getErrMsg());System.out.println("RequestId: " + e.getRequestId());}}
}
需要注意的是:
1、durationSeconds的最大取值是角色最大会话时间,如需调整最大实现需要前往RAM控制台进行修改。
2、由于频繁地调用STS服务会引起限流,因此需要对STS临时凭证做缓存处理,并在有效期前刷新。例如可以使用Redis进行缓存操作。
将应用服务器获取到的STS临时访问凭证credentials传递给前端,前端通过解析credentials即可获得相应的凭证,从而通过凭证创建OSSClient并上传文件。
5.2、客户端代码实例
// 从cookie中获取sts
let stsParameter = Base64.decode(getCookie("sts"));
// 如果cookie中没有,则向后端发起请求获取sts
if (stsParameter.length === 0) {await that.$axios.get("/user/sts").then(res => {stsParameter = res.data.datasetCookie("sts", Base64.encode(JSON.stringify(stsParameter)), 60 * 60 * 2)})
} else {stsParameter = JSON.parse(stsParameter)
}
let cli = client(stsParameter);export function client(data) {return new OSS({region: data.region,accessKeyId: data.credentials.accessKeyId,accessKeySecret: data.credentials.accessKeySecret,bucket: data.bucketName,stsToken: data.credentials.securityToken,refreshSTSToken: async () => {const refreshToken = await axios.get("http://localhost:8890/api/user/sts");return {accessKeyId: refreshToken.credentials.accessKeyId,accessKeySecret: refreshToken.credentials.AccessKeySecret,stsToken: refreshToken.credentials.securityToken,};},})
}
前端通过解析credentials即可获得相应的凭证,从而通过凭证创建OSSClient并获得上传文件的权限。
【博主推荐】
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类_java concurrent哪些类-CSDN博客https://blog.csdn.net/zzzzzhxxx/article/details/136777947?spm=1001.2014.3001.5502【网络原理】TCP 协议中比较重要的一些特性(三)-CSDN博客https://blog.csdn.net/zzzzzhxxx/article/details/136597348?spm=1001.2014.3001.5502【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列_单例模式懒汉和饿汉 java s1==s2-CSDN博客https://blog.csdn.net/zzzzzhxxx/article/details/136688859?spm=1001.2014.3001.5502
如果觉得作者写的不错,求给博主一个点赞支持一下,你们的支持是我更新的最大动力!
如果觉得作者写的不错,求给博主一个点赞支持一下,你们的支持是我更新的最大动力!
如果觉得作者写的不错,求给博主一个点赞支持一下,你们的支持是我更新的最大动力!