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 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 响应和服务器时间。"); } } } }