Sushant
Sushant

Reputation: 173

Avoid clear text passwords in JNDI datasource in Tomcat

I am using a JNDI datasource that is configured in tomcat server. I want to avoid storing the password as clear text and also i have an existing encryption logic available in the application used which i want to use to encrypt the database password.

<Resource name="jdbc/testdb" auth="Container"
      factory="com.zaxxer.hikari.HikariJNDIFactory"
      type="javax.sql.DataSource"
      minimumIdle="5" 
      maximumPoolSize="50"
      connectionTimeout="300000"
      driverClassName="org.mariadb.jdbc.Driver"
      jdbcUrl="jdbc:mysql://localhost:3307/testdb"
      dataSource.implicitCachingEnabled="true" 
      connectionTestQuery="Select 1" />

Considering this use case and the possible solutions available online i decided to use org.springframework.jdbc.datasource.UserCredentialsDataSourceAdapter for providing the username and password for the database using the code

<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/jdbc/testdb" />
    </bean>

<bean id="dataSource" class="org.springframework.jdbc.datasource.UserCredentialsDataSourceAdapter">
   <property name="targetDataSource" ref="dataSource1"/>
   <property name="username" value="${dataSource.username}"/>
   <property name="password" value="#{passwordDecryptor.decryptedString}"/>
 </bean>

This approach works for me for making connections to MSSQL database but quite strangely fails on MariaDB with the error "Access denied for user ''@'localhost' (Using Password :NO)". I wonder if this issue has got anything to do with the HikariCP connection pool, as the same works with C3P0 implementation without any issues.

Also I would like to know if this is the right approach and please suggest if this can improved for getting better performance.

Upvotes: 1

Views: 2038

Answers (1)

brettw
brettw

Reputation: 11114

Ok, I've taking a peek around, give this a shot:

<Resource name="jdbc/testdb" auth="Container"
    factory="org.apache.naming.factory.BeanFactory"
    type="com.zaxxer.hikari.HikariDataSource"
    minimumIdle="5" 
    maximumPoolSize="50"
    connectionTimeout="300000"
    driverClassName="org.mariadb.jdbc.Driver"
    jdbcUrl="jdbc:mysql://localhost:3307/testdb"
    connectionTestQuery="Select 1" />

What is different? We're not using the com.zaxxer.hikari.HikariJNDIFactory. Why? Because the HikariJNDIFactory uses the HikariDataSource(HikariConfig config) constructor, which immediately instantiates the underlying datasource, after which the configuration can no longer be changed.

Using the BeanFactory, we call the default constructor, which does not instantiate the underlying datasource until the first call to getConnection(), which means we are free to set the username/password after the JNDI lookup.

On the Spring side:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">

<jee:jndi-lookup id="dataSource"
   jndi-name="jdbc/testdb"
   cache="true"
   lookup-on-startup="true"
   expose-access-context="true">
     <property name="dataSourceProperties">
        <props>
           <prop key="user">${dataSource.username}</prop>
           <prop key="password">${passwordDecryptor.decryptedString}</prop>
        </props>
     </property>
</jee:jndi-lookup>

I believe this should work, or something very close to it. I am going to make an addition to HikariCP too honor the passing of the JNDI environment so that <jee:environment> can be used inside of the <jee:jndi-lookup> tag for a cleaner solution.

Upvotes: 2

Related Questions