102 lines
4.3 KiB
C#
102 lines
4.3 KiB
C#
using System.Globalization;
|
||
using System.Text.Json;
|
||
using Aliyun.Acs.Core;
|
||
using Aliyun.Acs.Core.Exceptions;
|
||
using Aliyun.Acs.Core.Http;
|
||
using Aliyun.Acs.Core.Profile;
|
||
using Aliyun.OSS;
|
||
using Microsoft.Extensions.Options;
|
||
|
||
namespace OssZipUploaderService
|
||
{
|
||
public class OssClientFactory
|
||
{
|
||
private readonly OssOptions _options;
|
||
|
||
public OssClientFactory(IOptions<OssOptions> options)
|
||
{
|
||
_options = options.Value;
|
||
}
|
||
|
||
// 每次上传前创建客户端,不复用上一次上传的临时凭据。
|
||
public OssClient CreateClient()
|
||
{
|
||
var accessKeyId = OssTJiaUploaderService.CryptoHelper.Decrypt(_options.AccessKeyId);
|
||
var accessKeySecret = OssTJiaUploaderService.CryptoHelper.Decrypt(_options.AccessKeySecret);
|
||
|
||
if (string.IsNullOrWhiteSpace(_options.RoleArn))
|
||
{
|
||
return new OssClient(_options.Endpoint, accessKeyId, accessKeySecret);
|
||
}
|
||
|
||
var profile = DefaultProfile.GetProfile(_options.StsRegionId, accessKeyId, accessKeySecret);
|
||
var stsClient = new DefaultAcsClient(profile);
|
||
CommonResponse response;
|
||
try
|
||
{
|
||
response = stsClient.GetCommonResponse(BuildAssumeRoleRequest());
|
||
}
|
||
catch (ClientException ex)
|
||
{
|
||
// 不将原始响应和凭据写入日志或钉钉消息。
|
||
throw new InvalidOperationException(
|
||
$"STS 扮演角色失败,错误码:{ex.ErrorCode},RequestId:{ex.RequestId}。" +
|
||
"请检查 RAM 用户的 sts:AssumeRole 权限、角色信任策略及 STS 配置。");
|
||
}
|
||
|
||
return CreateClientFromResponse(response.Data);
|
||
}
|
||
|
||
private CommonRequest BuildAssumeRoleRequest()
|
||
{
|
||
var request = new CommonRequest
|
||
{
|
||
Domain = _options.StsEndpoint,
|
||
Version = "2015-04-01",
|
||
Action = "AssumeRole",
|
||
Protocol = ProtocolType.HTTPS,
|
||
Method = MethodType.POST,
|
||
TimeoutInMilliSeconds = 30000
|
||
};
|
||
request.AddQueryParameters("RoleArn", _options.RoleArn);
|
||
request.AddQueryParameters("RoleSessionName", _options.RoleSessionName);
|
||
request.AddQueryParameters("DurationSeconds", _options.StsDurationSeconds.ToString(CultureInfo.InvariantCulture));
|
||
return request;
|
||
}
|
||
|
||
private OssClient CreateClientFromResponse(string responseData)
|
||
{
|
||
try
|
||
{
|
||
using var document = JsonDocument.Parse(responseData);
|
||
var credentials = document.RootElement.GetProperty("Credentials");
|
||
var accessKeyId = credentials.GetProperty("AccessKeyId").GetString();
|
||
var accessKeySecret = credentials.GetProperty("AccessKeySecret").GetString();
|
||
var securityToken = credentials.GetProperty("SecurityToken").GetString();
|
||
var expiration = credentials.GetProperty("Expiration").GetString();
|
||
|
||
if (string.IsNullOrWhiteSpace(accessKeyId) ||
|
||
string.IsNullOrWhiteSpace(accessKeySecret) ||
|
||
string.IsNullOrWhiteSpace(securityToken) ||
|
||
!DateTimeOffset.TryParse(expiration, CultureInfo.InvariantCulture,
|
||
DateTimeStyles.AssumeUniversal, out var expiresAt))
|
||
{
|
||
throw new InvalidOperationException("STS 返回的临时凭据不完整或过期时间无效。");
|
||
}
|
||
|
||
if (expiresAt <= DateTimeOffset.UtcNow.AddMinutes(1))
|
||
{
|
||
throw new InvalidOperationException("STS 临时凭据已过期或即将过期,请检查服务器时间。");
|
||
}
|
||
|
||
return new OssClient(_options.Endpoint, accessKeyId, accessKeySecret, securityToken);
|
||
}
|
||
catch (Exception ex) when (ex is JsonException || ex is KeyNotFoundException || ex is InvalidOperationException)
|
||
{
|
||
// JSON 解析异常可能包含响应片段,因此只返回固定错误信息。
|
||
throw new InvalidOperationException("STS 返回的临时凭据无效、已过期或即将过期,请检查 STS 响应和服务器时间。");
|
||
}
|
||
}
|
||
}
|
||
}
|