1、STS临时授权访问OSS
OSS 可以通过阿里云STS(Security Token Service)进行临时授权访问。通过STS,可以为第三方应用或子用户(即用户身份由自己管理的用户)颁发一个自定义时效和权限的访问凭证。
1、使用场景
对于您本地身份系统所管理的用户,例如您的App的用户、您的企业本地账号、第三方App的用户,将这部分用户称为联盟用户。此外,联盟用户还可以是您创建的能访问您的阿里云资源应用程序的用户。这些联盟用户可能需要直接访问OSS资源。
对于这部分联盟用户,通过阿里云STS服务为阿里云账号(或RAM用户)提供临时访问权限管理。您不需要透露云账号(或RAM用户)的长期密钥(如登录密码、AccessKey),只需要生成一个临时访问凭证给联盟用户使用即可。这个凭证的访问权限及有效期限都可以由您自定义。您不需要关心权限撤销问题,临时访问凭证过期后会自动失效。
通过STS生成的临时访问凭证包括安全令牌 (SecurityToken)、临时访问密钥STS AK(AccessKeyId和AccessKeySecret)。使用AccessKey方法与您在使用阿里云账户或RAM用户AccessKey发送请求时的方法相同。需要注意的是在每个向OSS发送的请求中必须携带安全令牌。
2、实现原理
以一个移动App举例。假设你是一个移动App开发者,打算使用阿里云OSS服务来保存App的终端用户数据,并且保证每个App用户之间的数据隔离,放置一个App用户获取到其他App用户的数据。你可以使用STS授权用户直接访问OSS。
使用STS授权用户直接访问OSS的流程如下:
-
App用户登录。App用户和云账号无关,它是App的终端用户,App服务器支持App用户登录。对于每个有效的App用户来说,需要App服务器能定义出每个App用户的最小访问权限。
-
App服务器请求STS服务获取一个安全令牌(SecurityToken)。在调用STS之前,App服务器需要确定App用户的最小访问权限(用RAM Policy来自定义授权策略)以及凭证的过期时间。然后通过扮演角色(AssumeRole)来获取一个代表角色身份的安全令牌(SecurityToken)。
-
STS返回给App服务器一个临时访问凭证,包括一个安全令牌(SecurityToken)、临时访问密钥(AccessKeyId和AccessKeySecret)以及过期时间。
-
App服务器将临时访问凭证返回给App客户端,App客户端可以缓存这个凭证。当凭证失效时,App客户端需要向App服务器申请新的临时访问凭证。例如,临时访问凭证有效期为1小时,那么App客户端可以每30分钟向App服务器请求更新临时访问凭证。
-
App客户端使用本地缓存的临时访问凭证去请求OSS API。OSS收到访问请求后,会通过STS服务来验证访问凭证,正确响应用户请求。
3、操作步骤
1、创建子账号
复制保存一下创建用户的accessKeyId和accessKeySecret,后面代码中会用到,然后点击添加权限,为其添加 AliyunSTSAssumeRoleAccess
权限。
2、创建权限策略
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:PutObject"
],
"Resource": [
"acs:oss:*:*:bucketName",
"acs:oss:*:*:bucketName/*"
]
}
]
}
3、创建角色并记录角色ARN
为创建的角色添加第二步创建的自定义权限策略:
复制保存创建角色的ARN,后面代码中用到:
4、调用STS服务接口AssumeRole获取临时访问凭证
1、pom.xml文件添加依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-sts</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.4.6</version>
</dependency>
2、后端代码实现
@Configuration
@ConfigurationProperties(prefix = "ali.oss")
@Data
public class AliOssConfig {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String roleArn;
private String regionId;
private String bucket;
}
ali:
oss:
endpoint: oss-cn-shenzhen.aliyuncs.com
access-key-id: 用户的accessKeyId
access-key-secret: 用户的accessKeySecret
role-arn: 角色的ARN
region-id: cn-shenzhen
bucket: best-favorites
调用AssumeRole接口之后返回给前端的对象
@Data
@Builder
public class AliOssTokenVo {
private String region;
private String accessKeyId;
private String accessKeySecret;
private String stsToken;
private String bucket;
}
获取临时访问凭证的方法
@Override
public AliOssTokenVo getOssToken() throws ClientException {
IClientProfile profile = DefaultProfile.getProfile(aliOssConfig.getRegionId(), aliOssConfig.getAccessKeyId(), aliOssConfig.getAccessKeySecret());
DefaultAcsClient client = new DefaultAcsClient(profile);
final AssumeRoleRequest request = new AssumeRoleRequest();
request.setRoleArn(aliOssConfig.getRoleArn());
request.setRoleSessionName("best-favorites");
request.setDurationSeconds(1000L);
AssumeRoleResponse response = client.getAcsResponse(request);
AssumeRoleResponse.Credentials credentials = response.getCredentials();
String accessKeyId = credentials.getAccessKeyId();
String accessKeySecret = credentials.getAccessKeySecret();
String securityToken = credentials.getSecurityToken();
return AliOssTokenVo.builder()
.accessKeyId(accessKeyId)
.accessKeySecret(accessKeySecret)
.stsToken(securityToken)
.region("oss-" + aliOssConfig.getRegionId())
.bucket(aliOssConfig.getBucket())
.build();
}
3、前端代码实现
api,调用后端接口获取临时访问凭证ossToken
import service from '@/utils/request';
export const getOssToken = () => service.get('/ali-oss/token');
store/modules/oss模块
import { getOssToken } from '@/api/business';
const oss = {
state: {
accessKeyId: '',
accessKeySecret: '',
stsToken: '',
region: '',
bucket: '',
},
mutations: {
SET_OSS_TOKEN: (state, { accessKeyId, accessKeySecret, stsToken, region, bucket }) => {
state.accessKeyId = accessKeyId;
state.accessKeySecret = accessKeySecret;
state.stsToken = stsToken;
state.region = region;
state.bucket = bucket;
},
},
actions: {
GetOssToken({ commit }) {
return new Promise((resolve, reject) => {
getOssToken()
.then(res => {
const ossToken = res.result;
commit('SET_OSS_TOKEN', ossToken);
resolve(ossToken);
})
.catch(error => {
reject(error);
});
});
},
},
};
export default oss;
上传组件:
<div class="clearfix">
<a-upload
list-type="picture-card"
:file-list="fileList"
:before-upload="beforeUpload"
:custom-request="customRequest"
@preview="handlePreview"
@change="handleChange"
>
<div v-if="fileList.length < 1">
<a-icon type="plus" />
<div class="ant-upload-text">
Upload
</div>
</div>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</div>
beforeUpload(file) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
this.$message.error('只能上传图片!');
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('上传图片必须小于10M!');
}
return isJpgOrPng && isLt10M;
},
async handleUpload({ file }) {
try {
let ossToken = await this.$store.dispatch('GetOssToken');
const client = new OSS({
accessKeyId: ossToken.accessKeyId,
accessKeySecret: ossToken.accessKeySecret,
stsToken: ossToken.stsToken,
region: ossToken.region,
bucket: ossToken.bucket,
});
let result = await client.put(file.name, file);
console.log('result:', result);
file.url = result.url;
this.fileList = [...this.fileList, file];
this.$emit('uploadSuccess', file.url);
this.$message.success('文件上传成功!');
} catch (e) {
console.log(e);
this.$message.error('文件上传失败!');
}
},
由于是使用web端直传,点击上传之后可能会出现如下错误,说明阿里云oss需要配置一下跨域访问
点击确定之后,重新上传,可以看到这时就已经上传成功!