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需要配置一下跨域訪問
點擊確定之后,重新上傳,可以看到這時就已經上傳成功!