使用 IAM 身份验证和 Spring JDBC(DataSource 和 JdbcTemplace)访问 AWS RDS
Posted
技术标签:
【中文标题】使用 IAM 身份验证和 Spring JDBC(DataSource 和 JdbcTemplace)访问 AWS RDS【英文标题】:Accessing AWS RDS using IAM Authentication and Spring JDBC (DataSource and JdbcTemplace) 【发布时间】:2018-11-23 16:02:07 【问题描述】:我无法弄清楚如何实现这一点。任何帮助和/或指示将不胜感激。
目前,我的 Java/Spring 应用程序后端部署在 EC2 上,并使用常规 Spring JDBC 设置成功访问 RDS 上的 MySQL。即在application.properties
中存储数据库信息,在@Configuration类中配置DataSource
和JdbcTemplate
。一切正常。
现在,我需要安全地访问 RDS 上的 mysql。 RDS 实例启用了 IAM 身份验证。我还成功创建了IAM 角色并应用了内联策略。然后,按照this link 上的 AWS RDS 文档和 Java 示例,我可以使用 Authentication Token 和我创建的用户成功地从 独立 Java 类 访问数据库而不是常规的数据库用户名和密码。这个独立的 Java 类直接处理“Connection”对象。
我卡住的地方是我如何将其转换为 Spring JDBC 配置。也就是说,在我的@Configuration 类中为此设置DataSource
和JdbcTemplate
bean。
实现这一点的正确/正确方法是什么?
-- 编辑 - 开始 -----
我正在尝试将其实现为可用于多个项目的库。也就是说,它将用作 JAR 并在项目的 POM 文件中声明为依赖项。该库将包括可配置的 AWS 服务,例如使用通用数据库用户名和密码的 RDS 访问、使用 IAM 身份验证的 RDS 访问、用于数据加密的 KMS(CMK/数据密钥)等。
想法是根据项目在任何网络/应用服务器上使用这个库。
希望这能进一步阐明我的需求。
----- 编辑 - 结束 -----
DataSource 内部有 getConnection() 所以我基本上可以创建自己的 DataSource 实现来实现我想要的。但这是一个好方法吗?
类似:
public class MyDataSource implements DataSource
@Override
public Connection getConnection() throws SQLException
Connection conn = null;
// get a connection using IAM Authentication Token for accessing AWS RDS, etc. as in the AWS docs
return conn;
@Override
public Connection getConnection(String username, String password) throws SQLException
return getConnection();
//other methods
【问题讨论】:
有人吗?我不能成为第一个尝试这样做的人...... 【参考方案1】:你可以使用下面的 sn -p 作为 SpringBoot/Tomcat 提供的默认连接池的替代。它将每 10 分钟刷新一次令牌密码,因为令牌有效期为 15 分钟。此外,它假设可以从 DNS 主机名中提取该区域。如果不是这种情况,您需要指定要使用的区域。
public class RdsIamAuthDataSource extends org.apache.tomcat.jdbc.pool.DataSource
private static final Logger LOG = LoggerFactory.getLogger(RdsIamAuthDataSource.class);
/**
* The Java KeyStore (JKS) file that contains the Amazon root CAs
*/
public static final String RDS_CACERTS = "/rds-cacerts";
/**
* Password for the ca-certs file.
*/
public static final String PASSWORD = "changeit";
public static final int DEFAULT_PORT = 3306;
@Override
public ConnectionPool createPool() throws SQLException
return pool != null ? pool : createPoolImpl();
protected synchronized ConnectionPool createPoolImpl() throws SQLException
return pool = new RdsIamAuthConnectionPool(poolProperties);
public static class RdsIamAuthConnectionPool extends ConnectionPool implements Runnable
private RdsIamAuthTokenGenerator rdsIamAuthTokenGenerator;
private String host;
private String region;
private int port;
private String username;
private Thread tokenThread;
public RdsIamAuthConnectionPool(PoolConfiguration prop) throws SQLException
super(prop);
@Override
protected void init(PoolConfiguration prop) throws SQLException
try
URI uri = new URI(prop.getUrl().substring(5));
this.host = uri.getHost();
this.port = uri.getPort();
if (this.port < 0)
this.port = DEFAULT_PORT;
this.region = StringUtils.split(this.host,'.')[2]; // extract region from rds hostname
this.username = prop.getUsername();
this.rdsIamAuthTokenGenerator = RdsIamAuthTokenGenerator.builder().credentials(new DefaultAWSCredentialsProviderChain()).region(this.region).build();
updatePassword(prop);
final Properties props = prop.getDbProperties();
props.setProperty("useSSL","true");
props.setProperty("requireSSL","true");
props.setProperty("trustCertificateKeyStoreUrl",getClass().getResource(RDS_CACERTS).toString());
props.setProperty("trustCertificateKeyStorePassword", PASSWORD);
super.init(prop);
this.tokenThread = new Thread(this, "RdsIamAuthDataSourceTokenThread");
this.tokenThread.setDaemon(true);
this.tokenThread.start();
catch (URISyntaxException e)
throw new RuntimeException(e.getMessage());
@Override
public void run()
try
while (this.tokenThread != null)
Thread.sleep(10 * 60 * 1000); // wait for 10 minutes, then recreate the token
updatePassword(getPoolProperties());
catch (InterruptedException e)
LOG.debug("Background token thread interrupted");
@Override
protected void close(boolean force)
super.close(force);
Thread t = tokenThread;
tokenThread = null;
if (t != null)
t.interrupt();
private void updatePassword(PoolConfiguration props)
String token = rdsIamAuthTokenGenerator.getAuthToken(GetIamAuthTokenRequest.builder().hostname(host).port(port).userName(this.username).build());
LOG.debug("Updated IAM token for connection pool");
props.setPassword(token);
请注意,您需要导入亚马逊的根/中间证书以建立可信连接。上面的示例代码假定证书已被导入到名为“rds-cacert”的文件中,并且在类路径中可用。或者,您也可以将它们导入到 JVM 'cacerts' 文件中。
要使用此数据源,您可以为 Spring 使用以下属性:
datasource:
url: jdbc:mysql://dbhost.xyz123abc.us-east-1.rds.amazonaws.com/dbname
username: iam_app_user
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.mydomain.jdbc.RdsIamAuthDataSource
使用 Spring Java 配置:
@Bean public DataSource dataSource()
PoolConfiguration props = new PoolProperties();
props.setUrl("jdbc:mysql://dbname.abc123xyz.us-east-1.rds.amazonaws.com/dbschema");
props.setUsername("iam_dbuser_app");
props.setDriverClassName("com.mysql.jdbc.Driver");
return new RdsIamAuthDataSource(props);
更新:在使用 MySQL 时,您还可以决定使用 MariaDB JDBC 驱动程序,它内置了对 IAM 身份验证的支持:
spring:
datasource:
host: dbhost.cluster-xxx.eu-west-1.rds.amazonaws.com
url: jdbc:mariadb:aurora//$spring.datasource.host/db?user=xxx&credentialType=AWS-IAM&useSsl&serverSslCert=classpath:rds-combined-ca-bundle.pem
type: org.mariadb.jdbc.MariaDbPoolDataSource
以上需要 MariaDB 和 AWS SDK 库,并且需要类路径中的 CA-bundle
【讨论】:
blagerweij - 这太棒了!由于RDS连接的限制,我没有想到要走连接池的路线。如果我到达那个阶段,这可能是我的下一个问题!感谢您的大力帮助,并通过正确的实施指明了这个方向,非常感谢!我需要再请求您一个帮助,您能否提供一个使用它的示例代码?提前谢谢你。 如果您使用的是 Spring Boot,您需要做的就是添加一个带有 JdbcTemplate 或 DataSource 的依赖项。 (例如@Autowired JdbcTemplate jdbcTemplate
)
嗨 blagerweij - 我已经用更多信息和正确的需求更新了我的原始问题(我认为)。你能看看并提供一些指示吗?非常感谢您的帮助!谢谢。
请注意,这并不是说不需要您的上述解决方案。事实上,它是解决 RDS 连接限制的完美方法。但我无法将它用于创建库。
上述解决方案使用DefaultAWSCredentialsProviderChain
,这意味着它支持所有可用的机制(环境变量、配置文件、Java 属性等)。对于许多需要 AWS 特定服务的项目,我们在共享库中使用上述 sn-p。 Spring Boot 用于配置 bean 属性,您唯一需要的是指定 DataSource 的类名 (type: com.mydomain.jdbc.RdsIamAuthDataSource
)【参考方案2】:
我知道这是一个较老的问题,但经过一番搜索后,我发现了一种非常简单的方法,您现在可以使用 MariaDB 驱动程序来执行此操作。在 2.5 版中,他们向驱动程序添加了 AWS IAM credential plugin。它将自动处理生成、缓存和刷新令牌。
我已经使用带有默认 HikariCP 连接池的 Spring Boot 2.3 进行了测试,使用这些设置对我来说效果很好:
spring.datasource.url=jdbc:mariadb://host/db?credentialType=AWS-IAM&useSsl&serverSslCert=classpath:rds-combined-ca-bundle.pem
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=iam_username
#spring.datasource.password=dont-need-this
spring.datasource.hikari.maxLifetime=600000
下载rds-combined-ca-bundle.pem并将其放入src/main/resources
,以便您可以通过SSL连接。
您还需要类路径上的这些依赖项:
runtime 'org.mariadb.jdbc:mariadb-java-client'
runtime 'com.amazonaws:aws-java-sdk-rds:1.11.880'
驱动程序使用标准的DefaultAWSCredentialsProviderChain
,因此请确保您拥有具有策略的凭据,该策略允许无论您在何处运行应用程序都可以访问 IAM 数据库。
希望这对其他人有所帮助 - 我在网上找到的大多数示例都涉及自定义代码、后台线程等 - 但使用新的驱动程序功能要容易得多!
【讨论】:
但它仍然需要AWS_SECRET_ACCESS_KEY
to be set...以上是关于使用 IAM 身份验证和 Spring JDBC(DataSource 和 JdbcTemplace)访问 AWS RDS的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Security 中通过 jdbc 身份验证使用自定义登录页面
带有 JDBC 身份验证的 Spring Security 5:UserDetailsService bean 仍然在内存中,而不是 JDBC
无法在 Spring Boot 中使用 JDBC 身份验证创建安全连接