web端文件直传阿里OSS

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的流程如下:

img

  1. App用户登录。App用户和云账号无关,它是App的终端用户,App服务器支持App用户登录。对于每个有效的App用户来说,需要App服务器能定义出每个App用户的最小访问权限。

  2. App服务器请求STS服务获取一个安全令牌(SecurityToken)。在调用STS之前,App服务器需要确定App用户的最小访问权限(用RAM Policy来自定义授权策略)以及凭证的过期时间。然后通过扮演角色(AssumeRole)来获取一个代表角色身份的安全令牌(SecurityToken)。

  3. STS返回给App服务器一个临时访问凭证,包括一个安全令牌(SecurityToken)、临时访问密钥(AccessKeyId和AccessKeySecret)以及过期时间。

  4. App服务器将临时访问凭证返回给App客户端,App客户端可以缓存这个凭证。当凭证失效时,App客户端需要向App服务器申请新的临时访问凭证。例如,临时访问凭证有效期为1小时,那么App客户端可以每30分钟向App服务器请求更新临时访问凭证。

  5. App客户端使用本地缓存的临时访问凭证去请求OSS API。OSS收到访问请求后,会通过STS服务来验证访问凭证,正确响应用户请求。

3、操作步骤

1、创建子账号

img

复制保存一下创建用户的accessKeyId和accessKeySecret,后面代码中会用到,然后点击添加权限,为其添加 AliyunSTSAssumeRoleAccess 权限。

img

img

2、创建权限策略

{
    "Version": "1",
    "Statement": [
     {
           "Effect": "Allow",
           "Action": [
             "oss:PutObject"
           ],
           "Resource": [
             "acs:oss:*:*:bucketName",
             "acs:oss:*:*:bucketName/*"
           ]
     }
    ]
}

img

3、创建角色并记录角色ARN

img

为创建的角色添加第二步创建的自定义权限策略:

img

img

复制保存创建角色的ARN,后面代码中用到:

img

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需要配置一下跨域访问

img

img

img

点击确定之后,重新上传,可以看到这时就已经上传成功!

posted @ 2021-10-13 17:28  喜欢小让  阅读(1827)  评论(0编辑  收藏  举报