Skip to content

Commit

Permalink
Merge pull request #76 from AlibabaCloudLandingZone/solution-oss-pre-…
Browse files Browse the repository at this point in the history
…signed/0.0.1

solution-oss-pre-signed/0.0.1
  • Loading branch information
wibud authored Sep 26, 2024
2 parents 0d3569a + ea12c35 commit d6b6e37
Show file tree
Hide file tree
Showing 15 changed files with 913 additions and 0 deletions.
32 changes: 32 additions & 0 deletions solution/solution-oss-pre-signed/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### IntelliJ IDEA ###
.idea/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
93 changes: 93 additions & 0 deletions solution/solution-oss-pre-signed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 通过预签名机制在客户端实现OSS直传和下载

在典型的服务端和客户端架构下,常见的文件上传方式是服务端代理上传:客户端将文件上传到业务服务器,然后业务服务器将文件上传到OSS。在这个过程中,一份数据需要在网络上传输两次,会造成网络资源的浪费,增加服务端的资源开销。同时从OSS上传/下载文件需要使用RAM用户的访问密钥(AccessKey)来完成签名认证,但是在客户端中使用长期有效的访问密钥,可能会导致访问密钥泄露,进而引起安全问题。

本文档介绍了一种在客户端实现直接从OSS上传/下载文件的方案,避免了业务服务器中转文件,提高了上传速度,节省了服务器资源,同时,客户端基于预签名机制访问OSS,无需透露长期AccessKey,减少密钥泄露的风险。

这里针对本方案提供了相关代码示例,帮助您快速完成应用改造,减少开发和部署的复杂度。


## 使用步骤

### 目录结构说明

```
.
└── code-example
└── java
└── spring-boot # Java SpringBoot示例代码
```

### Java示例代码

```
java/spring-boot/src/main
├── java/org/example
│ ├── Application.java
│ ├── config
│ │ ├── CredentialConfig.java # 初始化凭据客户端
│ │ └── OssConfig.java # 初始化OSS客户端等
│ ├── controller
│ │ ├── DownloadController.java
│ │ └── UploadController.java
│ ├── model
│ │ ├── OssPostCallback.java
│ │ ├── PostCallbackResp.java
│ │ └── PostSignatureResp.java
│ └── service
│ ├── DownloadService.java # 生成客户端可以直接下载的签名URL
│ └── UploadService.java # 生成客户端直传的Post Policy和签名,并处理OSS回调逻辑
└── resources
├── application.properties
└── static # 前端示例
└── index.html
```

#### 环境要求

该示例代码需要在ECS环境中执行,执行前,请确保运行环境中已配置好Java和Maven。

1. Java Development Kit (JDK):确保已安装Java 8或更高版本。
2. Apache Maven:确保已安装Maven 3.6.0或更高版本。

运行以下命令来检查Java安装:

```bash
java -version
```

运行以下命令来检查Maven安装:

```bash
maven -version
```

#### 本地运行

1. 首先您需要配置凭证信息,建议您通过环境变量进行配置:

```
ALIBABA_CLOUD_ACCESS_KEY_ID=<您的AccessKey ID>;ALIBABA_CLOUD_ACCESS_KEY_SECRET=<您的AccessKey Secret>
```
> 您也可以将该项目部署到阿里云上,强烈建议您使用临时凭证来代替固定AccessKey。
2. 接着您需要进行应用配置,打开 `resources/application.properties` 进行如下配置:
```
# 服务启动端口个
server.port = 7001
# 地域,以杭州地域为例
region.id=cn-hangzhou
# 请填写OSS Bucket名称,示例中会从该Bucket中上传下载文件
oss.bucket=
# 当前服务的请求地址,OSS会通过该地址回调到当前服务
service.address=
```
同时,在`resources/static/index.html`和`java/org/example/config/OssConfig.java`中配置对应的本地服务地址、OSS Bucket目录等信息。
3. 启动`Application.java`,浏览器打开`resources/static/index.html`体验Web端示例
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<groupId>org.example</groupId>
<artifactId>spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
<exec.cleanupDaemonThreads>false</exec.cleanupDaemonThreads>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>

<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>

<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea</artifactId>
<version>1.3.1</version>
</dependency>

<!-- oss-sdk -->
<!-- Requires: version >= 3.17.4 -->
<!-- 推荐使用最新版本 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>

<!-- Requires: version >= 0.3.4 -->
<!-- 推荐使用最新版本 -->
<!--获取所有已发布的版本列表,请参见https://github.com/aliyun/credentials-java/blob/master/ChangeLog.txt-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>credentials-java</artifactId>
<version>0.3.5</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.example.config;

import com.aliyun.credentials.Client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CredentialConfig {

// 初始化凭据客户端,Credential SDK Client 应该是单例,不要每次请求都重新 new 一个,避免内存泄露
// 借助Credentials工具的默认凭据链,您可以用同一套代码,通过程序之外的配置来控制不同环境下的凭据获取方式
// 当您在初始化凭据客户端不传入任何参数时,Credentials工具将会尝试按照如下顺序查找相关凭据信息(优先级由高到低):
// 1. 使用系统属性
// 2. 使用环境变量
// 3. 使用OIDC RAM角色
// 4. 使用配置文件
// 5. 使用ECS实例RAM角色(需要通过环境变量 ALIBABA_CLOUD_ECS_METADATA 指定 ECS 实例角色名称;通过环境变量 ALIBABA_CLOUD_ECS_IMDSV2_ENABLE=true 开启在加固模式下获取STS Token)
// https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-access-credentials#3ca299f04bw3c
// 要使用默认凭据链,初始化 Client 时,必须使用空的构造函数,不能配置 Config 入参
@Bean
Client getCredentialClient() {
return new Client();
}

// 除了使用上面的默认凭据链,您也可以在代码中显式配置,来初始化凭据客户端
// 如下所示,可以进行显式配置,以ECS实例角色为例
//@Bean
//Client getCredentialClient() {
// Config config = new Config()
// .setType("ecs_ram_role")
// // 选填,该ECS实例角色的角色名称,不填会自动获取,建议加上以减少请求次数
// .setRoleName("<请填写ECS实例角色的角色名称>")
// // 在加固模式下获取STS Token,强烈建议开启
// .setEnableIMDSv2(true);
// return new Client(config);
//}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.example.config;

import com.aliyun.credentials.models.CredentialModel;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.Credentials;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentials;
import com.aliyun.oss.common.comm.SignVersion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class OssConfig {

@Value("${region.id}")
String regionId;

@Value("${oss.bucket}")
String bucket;

@Value("${service.address}")
String serviceAddress;

String dir = "example/";

String endpoint;

String host;

// 上传文件时,post policy 的过期时间,单位为毫秒
long uploadExpireTime = 3600;

// 下载文件时,签名 url 的过期时间,单位为毫秒
long downloadExpireTime = 300;

String postCallbackUrl;

@Autowired
com.aliyun.credentials.Client credentialClient;

@PostConstruct
public void init() {
this.endpoint = "oss-" + regionId + ".aliyuncs.com";
this.host = "https://" + bucket + "." + endpoint;
this.postCallbackUrl = serviceAddress + "/upload/callback";
}

// 自定义 OSS Credentails Provider,通过该 Provder 初始化下方 OSS SDK Client
// 该 Provdier 会从 Credentail SDK Client 中获取最新的 STS Token
// Credentail SDK Client 会自动更新 STS Token,您无需关心 STS Token 到期如何更换的问题
@Bean
CredentialsProvider getOssCredentialsProvider() {
return new CredentialsProvider() {
@Override
public void setCredentials(Credentials credentials) {
}

@Override
public Credentials getCredentials() {
// 保证线程安全,从 CredentialModel 中获取 ak/sk/security token
CredentialModel credentialModel = credentialClient.getCredential();
String ak = credentialModel.getAccessKeyId();
String sk = credentialModel.getAccessKeySecret();
String token = credentialModel.getSecurityToken();
return new DefaultCredentials(ak, sk, token);
}
};
}

// 初始化阿里云 OSS SDK 客户端
// OSS SDK Client 建议是单例模式,不要每次请求都重新 New 一个,避免出现内存泄露的问题
@Bean
OSS getOssClient(CredentialsProvider credentialsProvider) {
// 建议使用更安全的V4签名算法,则初始化时需要加入endpoint对应的region信息,同时声明SignVersion.V4
// OSS Java SDK 3.17.4及以上版本支持V4签名。
ClientBuilderConfiguration configuration = new ClientBuilderConfiguration();
configuration.setSignatureVersion(SignVersion.V4);

return OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(configuration)
.region(regionId)
.build();
}

public String getBucket() {
return bucket;
}

public String getHost() {
return host;
}

public long getUploadExpireTime() {
return uploadExpireTime;
}

public String getPostCallbackUrl() {
return postCallbackUrl;
}

public String getDir() {
return dir;
}

public long getDownloadExpireTime() {
return downloadExpireTime;
}
}
Loading

0 comments on commit d6b6e37

Please sign in to comment.