java - 想要连接到同一个本地 S3 兼容的不同存储
问题描述
目前,我们正在本地 S3 兼容存储中连接到我们团队的存储桶。
配置类:
@Configuration
public class S3Config {
@Value("${aws.access_key_id}")
private String awsId;
@Value("${aws.secret_access_key}")
private String awsKey;
@Value("${s3.endpoint}")
private String endpoint;
@Value("${s3.keystorePD}")
private String keystorePD;
@Value("${s3.jksFile}")
private String jksFile;
private static final Logger log = LoggerFactory.getLogger(S3Config.class);
@PostConstruct
public void init()
{
log.info("Initializing SSL Configuration for S3 bucket");
System.setProperty(ApplicationConstants.KEY_STORE_TYPE,"jks");
System.setProperty(ApplicationConstants.TRUST_STORE_TYPE,"jks");
System.setProperty(ApplicationConstants.KEY_STORE_PROPERTY,Thread.currentThread().getContextClassLoader().getResource(jksFile).getFile());
System.setProperty(ApplicationConstants.TRUST_STORE_PROPERTY,Thread.currentThread().getContextClassLoader().getResource(jksFile).getFile());
System.setProperty(ApplicationConstants.KEY_STORE_PD,keystorePD);
System.setProperty(ApplicationConstants.TRUST_STORE_PD,keystorePD);
log.info("Successfully initialized SSL Configuration for S3 bucket");
}
@Bean
public AmazonS3 s3client() {
log.info("Initializing the S3 client");
AmazonS3 s3Client = null;
BasicAWSCredentials awsCreds = new BasicAWSCredentials(awsId,
awsKey);
s3Client = new AmazonS3Client(awsCreds);
s3Client.setEndpoint(endpoint);
return s3Client;
}
用法 :
@Autowired
AmazonS3 s3Client;
public void uploadFile(File file, String targetLocation, String bucketName) {
log.info("converted file size : " + file.getTotalSpace());
log.info("bucketName : " + bucketName);
log.info("targetLocation : " + targetLocation);
s3Client.putObject(bucketName, targetLocation, file);
}
我的要求是为每个传入请求动态创建客户端。因此,如果团队 A 通过,则使用团队 A 的 jks 文件的 s3 配置连接到存储。
我在用 :
- Spring Boot v2.3.1.RELEASE
- aws-sdk v1.11.560
- 打开JDK v11.0.1
我该如何做到这一点?
更新:
从应用程序开始,我将拥有 jks 文件、access_keys、access_ids。
解决方案
在这种情况下有两个独特的问题
- 使用非系统属性提供加密材料
- 为每个请求切换(并在需要时构建)凭据。
以下解决方案大纲将解决这两个问题。它使用 StaticEncryptionMaterialsProvider 来避免对系统属性的依赖。为了解决第二个问题,S3ClientResolver 使用一个 REQUEST 范围,将 proxyMode 作为 TARGET_CLASS 以确保在 spring 上下文中的接线在这种情况下正常工作。
@ConfigurationProperties(prefix = "teams")
@Component
public class CredentialsMap extends HashMap<String, S3Credentials> {
// key will be team name and value will have s3Credentials
}
public class S3Credentials {
private String awsId;
private String awsKey;
private String endpoint;
private String keystorePD;
private String jksFile;
/* Add all getter and setters as well */
}
/**
* Building client is an expensive operation. This provider lazily builds the clients
* and cache them using Map for subsequent usage without incurring built cost agian.
*/
public class S3ClientProvider {
@Autowired
private CredentialsMap credentialsMap;
private Map<String, AmazonS3> clients;
public AmazonS3 getClient(String client) {
if (!clients.containsKey(client))
clients.put(client, buildClient(client));
return clients.get(client);
}
private AmazonS3 buildClient(String client) {
S3Credentials cred = credentialsMap.get(client);
// TODO - add try/catch
KeyPair kp = getKeyPair(cred);
AWSStaticCredentialsProvider cp = new AWSStaticCredentialsProvider(new BasicAWSCredentials(
cred.getAwsId(), cred.getAwsKey()));
AmazonS3 as3 = AmazonS3EncryptionClientBuilder
.standard()
.withRegion(Regions.US_WEST_2)
.withCredentials(cp)
.withCryptoConfiguration(new CryptoConfiguration(CryptoMode.AuthenticatedEncryption))
.withEncryptionMaterials(new StaticEncryptionMaterialsProvider(new EncryptionMaterials(kp)))
.build();
as3.setEndpoint(cred.getEndpoint());
return as3;
}
private KeyPair getKeyPair(S3Credentials cred) throws Exception {
FileInputStream is = new FileInputStream(Thread.currentThread().getContextClassLoader()
.getResource(cred.getJksFile()).getFile());
KeyStore keystore = KeyStore.getInstance("jks");
keystore.load(is, cred.getKeystorePD().toCharArray());
// the only key in your JKS must have this alias
// you may also add the alias as another property in your configuration instead
String alias = "myS3Key";
Key key = keystore.getKey(alias, cred.getKeystorePD().toCharArray());
if (key instanceof PrivateKey) {
Certificate cert = keystore.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, (PrivateKey) key);
}
throw new RuntimeException("Invalid key");
}
}
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class S3ClientResolver {
/* You can inject current request OR any other bean for that matter here */
/* Spring authenticaiotn object to get access to current logged in user */
@Autowired
private Authentication authentication;
@Autowired
private S3ClientProvider provider;
public AmazonS3 getClient() {
// deduce client value from current authenticaiotn object as may be applicable
String client = "TODO";
return provider.getClient(client);
}
}
可以在 spring boot 的 application.yml 中指定不同客户端的配置,如下所示
teams:
one:
awsId: abc
awsKey: abc
endpoint: https://example.com
keystorePD: onejskpass
jksFile: one.jks
two:
awsId: abc2
awsKey: abc2
endpoint: https://example.com
keystorePD: twojskpass
jksFile: two.jks
有了这个,您的代码将如下所示
@Autowired
private S3ClientResolver resolver;
public void uploadFile(File file, String targetLocation, String bucketName) {
log.info("converted file size : " + file.getTotalSpace());
log.info("bucketName : " + bucketName);
log.info("targetLocation : " + targetLocation);
resolver.getClient().putObject(bucketName, targetLocation, file);
}
请根据自己的环境进行调整。我很快从我过去的一项类似工作中提取了这些碎片。我在上面的代码段中留下了许多项目作为评论。请仔细阅读并根据您的用例进行相应调整。
[编辑 - 2020 年 7 月 15 日]
jackfr0st,密钥对生成逻辑正在工作,我刚刚使用以下独立程序对其进行了验证,该程序是上面提到的内容的精确副本,并在线更正了 return 语句(之前缺少)new KeyPair(publicKey, (PrivateKey) key)
- 为此我生成了一个新的密钥库(testjks .jks) 使用以下命令 -keytool -genkey -alias mydomain -keyalg RSA -keystore keystore.jks -keysize 2048
import java.io.FileInputStream;
import java.security.*;
import java.security.cert.Certificate;
public class ExtractKeypair {
public static void main(String[] args) throws Exception {
KeyPair kp = generateKeypair("testjks.jks");
}
public static KeyPair generateKeypair(String file) throws Exception {
FileInputStream is = new FileInputStream(Thread.currentThread().getContextClassLoader()
.getResource(file).getFile());
KeyStore keystore = KeyStore.getInstance("jks");
keystore.load(is, "password".toCharArray());
// the only key in your JKS must have this alias
// you may also add the alias as another property in your configuration instead
String alias = "mydomain";
Key key = keystore.getKey(alias, "password".toCharArray());
if (key instanceof PrivateKey) {
Certificate cert = keystore.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, (PrivateKey) key);
}
throw new RuntimeException("Invalid key");
}
}
如果您仍然面临问题,我建议您查看返回的资源 ( Thread.currentThread().getContextClassLoader().getResource(file).getFile()
),因为如果不正确,这是唯一可能导致问题的变量。我用 oracle jdk 8 测试了上面的程序。
推荐阅读
- javascript - 如何使用js获取浏览器中打开的标签数量
- python - 为什么在两行上写 int(input()) 与一行不同?
- networking - Point-2-Point 网络中的中继器
- javascript - querySelector("selector") vs querySelector("selector").value
- django - DRF 不可写嵌套序列化器相关字段
- python - 删除日志文件后python记录丢失消息
- laravel - JSON/整数数组的 Laravel 验证不起作用
- questdb - 估计 QuestDB 的磁盘空间要求
- c# - 使用 HealthChecks 检查我的 Quartz 是否健康
- kotlin - Kotlin kmm 创建类型为:CValuesRef 的变量