|
@@ -2,6 +2,8 @@ package shop.alien.store.controller;
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.aliyun.oss.common.utils.BinaryUtil;
|
|
import com.aliyun.oss.common.utils.BinaryUtil;
|
|
|
|
|
+import com.aliyun.sts20150401.models.AssumeRoleResponse;
|
|
|
|
|
+import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import io.swagger.annotations.Api;
|
|
import io.swagger.annotations.Api;
|
|
|
import io.swagger.annotations.ApiOperation;
|
|
import io.swagger.annotations.ApiOperation;
|
|
@@ -14,10 +16,14 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.Mac;
|
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
-import java.time.*;
|
|
|
|
|
|
|
+import java.time.Instant;
|
|
|
|
|
+import java.time.ZoneId;
|
|
|
|
|
+import java.time.ZoneOffset;
|
|
|
|
|
+import java.time.ZonedDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
|
|
@@ -46,6 +52,25 @@ public class OSSDirectUploadNewController {
|
|
|
@Value("${ali.oss.bucketName}")
|
|
@Value("${ali.oss.bucketName}")
|
|
|
private String bucketName;
|
|
private String bucketName;
|
|
|
|
|
|
|
|
|
|
+ @Value("${ali.oss.ossStsRoleArn}")
|
|
|
|
|
+ private String ossStsRoleArn;
|
|
|
|
|
+
|
|
|
|
|
+ public static String STATIC_ACCESS_KEY_ID;
|
|
|
|
|
+ public static String STATIC_ACCESS_KEY_SECRET;
|
|
|
|
|
+ public static String STATIC_END_POINT;
|
|
|
|
|
+ public static String STATIC_BUCKET_NAME;
|
|
|
|
|
+ public static String OSS_STS_ROLE_ARN;
|
|
|
|
|
+ // 3. 初始化方法:对象创建后立即执行,把非静态值赋给静态变量
|
|
|
|
|
+ @PostConstruct
|
|
|
|
|
+ public void init() {
|
|
|
|
|
+ STATIC_ACCESS_KEY_ID = this.accessKeyId;
|
|
|
|
|
+ STATIC_ACCESS_KEY_SECRET = this.accessKeySecret;
|
|
|
|
|
+ STATIC_END_POINT = this.endPoint;
|
|
|
|
|
+ STATIC_BUCKET_NAME = this.bucketName;
|
|
|
|
|
+ OSS_STS_ROLE_ARN = this.ossStsRoleArn;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
// 从endPoint提取region(例如:oss-cn-beijing.aliyuncs.com -> cn-beijing)
|
|
// 从endPoint提取region(例如:oss-cn-beijing.aliyuncs.com -> cn-beijing)
|
|
|
private String getRegion() {
|
|
private String getRegion() {
|
|
|
if (endPoint != null && endPoint.contains("oss-")) {
|
|
if (endPoint != null && endPoint.contains("oss-")) {
|
|
@@ -58,6 +83,22 @@ public class OSSDirectUploadNewController {
|
|
|
return "cn-beijing"; // 默认值
|
|
return "cn-beijing"; // 默认值
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 OSS Endpoint 得到 STS Endpoint。获取临时凭证必须使用 STS 端点,不能使用 OSS 端点。
|
|
|
|
|
+ * 例如:oss-cn-beijing.aliyuncs.com -> sts.cn-beijing.aliyuncs.com
|
|
|
|
|
+ */
|
|
|
|
|
+ private static String getStsEndpointFromOssEndpoint(String ossEndpoint) {
|
|
|
|
|
+ if (ossEndpoint != null && ossEndpoint.contains("oss-")) {
|
|
|
|
|
+ int start = ossEndpoint.indexOf("oss-") + 4;
|
|
|
|
|
+ int end = ossEndpoint.indexOf(".aliyuncs.com");
|
|
|
|
|
+ if (end > start) {
|
|
|
|
|
+ String region = ossEndpoint.substring(start, end);
|
|
|
|
|
+ return "sts." + region + ".aliyuncs.com";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return "sts.cn-beijing.aliyuncs.com";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 构建host地址
|
|
// 构建host地址
|
|
|
private String getHost() {
|
|
private String getHost() {
|
|
|
return "https://" + bucketName + "." + endPoint;
|
|
return "https://" + bucketName + "." + endPoint;
|
|
@@ -99,31 +140,28 @@ public class OSSDirectUploadNewController {
|
|
|
return formattedDate;
|
|
return formattedDate;
|
|
|
}
|
|
}
|
|
|
//初始化STS Client(如果需要使用STS,取消注释并配置相关环境变量)
|
|
//初始化STS Client(如果需要使用STS,取消注释并配置相关环境变量)
|
|
|
- /*
|
|
|
|
|
public static com.aliyun.sts20150401.Client createStsClient() throws Exception {
|
|
public static com.aliyun.sts20150401.Client createStsClient() throws Exception {
|
|
|
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
|
|
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
|
|
|
// 建议使用更安全的 STS 方式。
|
|
// 建议使用更安全的 STS 方式。
|
|
|
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
|
|
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
|
|
|
// 必填,请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_ID。
|
|
// 必填,请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_ID。
|
|
|
- .setAccessKeyId(System.getenv("OSS_ACCESS_KEY_ID"))
|
|
|
|
|
|
|
+ .setAccessKeyId(STATIC_ACCESS_KEY_ID)
|
|
|
// 必填,请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_SECRET。
|
|
// 必填,请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_SECRET。
|
|
|
- .setAccessKeySecret(System.getenv("OSS_ACCESS_KEY_SECRET"));
|
|
|
|
|
- // Endpoint 请参考 https://api.aliyun.com/product/Sts
|
|
|
|
|
- config.endpoint = "sts.cn-hangzhou.aliyuncs.com";
|
|
|
|
|
|
|
+ .setAccessKeySecret(STATIC_ACCESS_KEY_SECRET);
|
|
|
|
|
+ // 获取临时凭证必须使用 STS 端点,不能使用 OSS 端点。参见 https://api.aliyun.com/product/Sts
|
|
|
|
|
+ config.endpoint = getStsEndpointFromOssEndpoint(STATIC_END_POINT);
|
|
|
return new com.aliyun.sts20150401.Client(config);
|
|
return new com.aliyun.sts20150401.Client(config);
|
|
|
}
|
|
}
|
|
|
- */
|
|
|
|
|
|
|
|
|
|
//获取STS临时凭证
|
|
//获取STS临时凭证
|
|
|
// 注意:此方法需要STS服务支持,如果不需要STS,可以直接使用AccessKey
|
|
// 注意:此方法需要STS服务支持,如果不需要STS,可以直接使用AccessKey
|
|
|
// 这里暂时注释掉STS相关代码,直接使用配置的AccessKey
|
|
// 这里暂时注释掉STS相关代码,直接使用配置的AccessKey
|
|
|
- /*
|
|
|
|
|
public static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials getCredential() throws Exception {
|
|
public static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials getCredential() throws Exception {
|
|
|
com.aliyun.sts20150401.Client client = OSSDirectUploadNewController.createStsClient();
|
|
com.aliyun.sts20150401.Client client = OSSDirectUploadNewController.createStsClient();
|
|
|
com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest()
|
|
com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest()
|
|
|
// 必填,请确保代码运行环境设置了环境变量 OSS_STS_ROLE_ARN
|
|
// 必填,请确保代码运行环境设置了环境变量 OSS_STS_ROLE_ARN
|
|
|
- .setRoleArn(System.getenv("OSS_STS_ROLE_ARN"))
|
|
|
|
|
- .setRoleSessionName("yourRoleSessionName");// 自定义会话名称
|
|
|
|
|
|
|
+ .setRoleArn(OSS_STS_ROLE_ARN)
|
|
|
|
|
+ .setRoleSessionName("ossDirectPass");// 自定义会话名称
|
|
|
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
|
|
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
|
|
|
try {
|
|
try {
|
|
|
// 复制代码运行请自行打印 API 的返回值
|
|
// 复制代码运行请自行打印 API 的返回值
|
|
@@ -131,21 +169,33 @@ public class OSSDirectUploadNewController {
|
|
|
// credentials里包含了后续要用到的AccessKeyId、AccessKeySecret和SecurityToken。
|
|
// credentials里包含了后续要用到的AccessKeyId、AccessKeySecret和SecurityToken。
|
|
|
return response.body.credentials;
|
|
return response.body.credentials;
|
|
|
} catch (Exception error) {
|
|
} catch (Exception error) {
|
|
|
|
|
+ String msg = error.getMessage() != null ? error.getMessage() : "";
|
|
|
|
|
+ if (msg.contains("NoSuchBucket") || msg.contains("bucket does not exist")) {
|
|
|
|
|
+ log.error("指定的Bucket不存在,请检查配置 ali.oss.bucketName: {}", STATIC_BUCKET_NAME, error);
|
|
|
|
|
+ throw new RuntimeException("指定的Bucket不存在,请检查配置 ali.oss.bucketName: " + STATIC_BUCKET_NAME, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("403") || msg.contains("not authorized") || msg.contains("authorized by RAM")) {
|
|
|
|
|
+ log.error("STS AssumeRole 无权限(403):当前 RAM 用户/角色未授权扮演角色,请到阿里云 RAM 控制台检查:1) 当前 AccessKey 对应用户需有 sts:AssumeRole 权限;2) 角色 {} 的信任策略需允许该用户扮演。", OSS_STS_ROLE_ARN, error);
|
|
|
|
|
+ throw new RuntimeException("STS 临时凭证无权限(403):请在 RAM 控制台为当前 AccessKey 对应用户授权 sts:AssumeRole,并确保角色 " + OSS_STS_ROLE_ARN + " 的信任策略允许该用户扮演。", error);
|
|
|
|
|
+ }
|
|
|
log.error("获取STS凭证失败: {}", error.getMessage(), error);
|
|
log.error("获取STS凭证失败: {}", error.getMessage(), error);
|
|
|
throw new RuntimeException("获取STS凭证失败", error);
|
|
throw new RuntimeException("获取STS凭证失败", error);
|
|
|
}
|
|
}
|
|
|
- return null;
|
|
|
|
|
}
|
|
}
|
|
|
- */
|
|
|
|
|
|
|
|
|
|
@ApiOperation("生成OSS直传签名(OSS4-HMAC-SHA256方式)")
|
|
@ApiOperation("生成OSS直传签名(OSS4-HMAC-SHA256方式)")
|
|
|
@GetMapping("/signature")
|
|
@GetMapping("/signature")
|
|
|
public ResponseEntity<Map<String, String>> getPostSignatureForOssUpload() {
|
|
public ResponseEntity<Map<String, String>> getPostSignatureForOssUpload() {
|
|
|
try {
|
|
try {
|
|
|
- // 直接使用配置的AccessKey(如果使用STS,需要调用getCredential()方法)
|
|
|
|
|
- String accesskeyid = accessKeyId;
|
|
|
|
|
- String accesskeysecret = accessKeySecret;
|
|
|
|
|
- String securitytoken = ""; // 如果使用STS,这里应该是securityToken
|
|
|
|
|
|
|
+// // 直接使用配置的AccessKey(如果使用STS,需要调用getCredential()方法)
|
|
|
|
|
+// String accesskeyid = accessKeyId;
|
|
|
|
|
+// String accesskeysecret = accessKeySecret;
|
|
|
|
|
+// String securitytoken = ""; // 如果使用STS,这里应该是securityToken
|
|
|
|
|
+ AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials sts_data = getCredential();
|
|
|
|
|
+
|
|
|
|
|
+ String accesskeyid = sts_data.accessKeyId;
|
|
|
|
|
+ String accesskeysecret = sts_data.accessKeySecret;
|
|
|
|
|
+ String securitytoken = sts_data.securityToken;
|
|
|
|
|
|
|
|
String region = getRegion();
|
|
String region = getRegion();
|
|
|
String host = getHost();
|
|
String host = getHost();
|
|
@@ -174,6 +224,13 @@ public class OSSDirectUploadNewController {
|
|
|
bucketCondition.put("bucket", bucketName);
|
|
bucketCondition.put("bucket", bucketName);
|
|
|
conditions.add(bucketCondition);
|
|
conditions.add(bucketCondition);
|
|
|
|
|
|
|
|
|
|
+ // 如果使用STS,需要添加security-token条件
|
|
|
|
|
+ if (securitytoken != null && !securitytoken.isEmpty()) {
|
|
|
|
|
+ Map<String, String> securityTokenCondition = new HashMap<>();
|
|
|
|
|
+ securityTokenCondition.put("x-oss-security-token", securitytoken);
|
|
|
|
|
+ conditions.add(securityTokenCondition);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Map<String, String> signatureVersionCondition = new HashMap<>();
|
|
Map<String, String> signatureVersionCondition = new HashMap<>();
|
|
|
signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
|
|
signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
|
|
|
conditions.add(signatureVersionCondition);
|
|
conditions.add(signatureVersionCondition);
|
|
@@ -186,12 +243,6 @@ public class OSSDirectUploadNewController {
|
|
|
dateCondition.put("x-oss-date", x_oss_date);
|
|
dateCondition.put("x-oss-date", x_oss_date);
|
|
|
conditions.add(dateCondition);
|
|
conditions.add(dateCondition);
|
|
|
|
|
|
|
|
- // 如果使用STS,需要添加security-token条件
|
|
|
|
|
- if (securitytoken != null && !securitytoken.isEmpty()) {
|
|
|
|
|
- Map<String, String> securityTokenCondition = new HashMap<>();
|
|
|
|
|
- securityTokenCondition.put("x-oss-security-token", securitytoken);
|
|
|
|
|
- conditions.add(securityTokenCondition);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
conditions.add(Arrays.asList("content-length-range", 1, 10240000));
|
|
conditions.add(Arrays.asList("content-length-range", 1, 10240000));
|
|
|
conditions.add(Arrays.asList("eq", "$success_action_status", "200"));
|
|
conditions.add(Arrays.asList("eq", "$success_action_status", "200"));
|
|
@@ -201,8 +252,8 @@ public class OSSDirectUploadNewController {
|
|
|
|
|
|
|
|
String jsonPolicy = mapper.writeValueAsString(policy);
|
|
String jsonPolicy = mapper.writeValueAsString(policy);
|
|
|
|
|
|
|
|
- // 步骤2:构造待签名字符串(StringToSign)。
|
|
|
|
|
- String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes()));
|
|
|
|
|
|
|
+ // 步骤2:构造待签名字符串(StringToSign)。使用 UTF-8 与文档/服务端一致,避免签名差异。
|
|
|
|
|
+ String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
|
|
|
|
|
|
|
// 步骤3:计算SigningKey。
|
|
// 步骤3:计算SigningKey。
|
|
|
byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), date);
|
|
byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), date);
|
|
@@ -225,8 +276,9 @@ public class OSSDirectUploadNewController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Map<String, String> response = new HashMap<>();
|
|
Map<String, String> response = new HashMap<>();
|
|
|
- // 将数据添加到 map 中
|
|
|
|
|
|
|
+ // 与文档一致:version / x_oss_signature_version 均返回,便于前端按文档示例取值
|
|
|
response.put("version", "OSS4-HMAC-SHA256");
|
|
response.put("version", "OSS4-HMAC-SHA256");
|
|
|
|
|
+ response.put("x_oss_signature_version", "OSS4-HMAC-SHA256");
|
|
|
// 这里是易错点,不能直接传policy,需要做一下Base64编码
|
|
// 这里是易错点,不能直接传policy,需要做一下Base64编码
|
|
|
response.put("policy", stringToSign);
|
|
response.put("policy", stringToSign);
|
|
|
response.put("x_oss_credential", x_oss_credential);
|
|
response.put("x_oss_credential", x_oss_credential);
|
|
@@ -238,6 +290,9 @@ public class OSSDirectUploadNewController {
|
|
|
}
|
|
}
|
|
|
response.put("dir", uploadDir);
|
|
response.put("dir", uploadDir);
|
|
|
response.put("host", host);
|
|
response.put("host", host);
|
|
|
|
|
+ // 前端 PostObject 表单字段名必须与 OSS 约定一致,否则会返回 MethodNotAllowed(405):
|
|
|
|
|
+ // policy, x-oss-signature(本接口返回 signature), x-oss-signature-version, x-oss-credential, x-oss-date,
|
|
|
|
|
+ // x-oss-security-token(有 STS 时), key(=dir+文件名), success_action_status, file(必须最后一项)
|
|
|
if (!base64CallbackBody.isEmpty()) {
|
|
if (!base64CallbackBody.isEmpty()) {
|
|
|
response.put("callback", base64CallbackBody);
|
|
response.put("callback", base64CallbackBody);
|
|
|
}
|
|
}
|