Tomcatのserver.xml
のリソース定義は次のようになります...
<Resource
name="jdbc/tox"
scope="Shareable"
type="javax.sql.DataSource"
url="jdbc:Oracle:thin:@yourDBserver.yourCompany.com:1521:yourDBsid"
driverClassName="Oracle.jdbc.pool.OracleDataSource"
username="tox"
password="toxbaby"
maxIdle="3"
maxActive="10"
removeAbandoned="true"
removeAbandonedTimeout="60"
testOnBorrow="true"
validationQuery="select * from dual"
logAbandoned="true"
debug="99"/>
パスワードは平文です。これを避ける方法は?
パスワードを暗号化する前に言ったように、問題を別の場所に移動しているだけです。
とにかく、それは非常に簡単です。秘密鍵などの静的フィールドと、パスワードを暗号化、復号化する静的メソッドを含むクラスを作成するだけです。 Tomcatの構成ファイルでパスワードを暗号化します(server.xml
またはyourapp.xml
...)このクラスを使用します。
Tomcatで「オンザフライ」でパスワードを復号化するには、DBCPのBasicDataSourceFactory
を拡張し、リソースでこのファクトリを使用します。
次のようになります。
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
カスタムファクトリの場合:
package mypackage;
....
public class MyCustomBasicDataSourceFactory extends org.Apache.Tomcat.dbcp.dbcp.BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
Object o = super.getObjectInstance(obj, name, nameCtx, environment);
if (o != null) {
BasicDataSource ds = (BasicDataSource) o;
if (ds.getPassword() != null && ds.getPassword().length() > 0) {
String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
ds.setPassword(pwd);
}
return ds;
} else {
return null;
}
}
お役に立てれば。
Tomcatはデータベースへの接続方法を知る必要があるため、プレーンテキストパスワードにアクセスする必要があります。パスワードが暗号化されている場合、Tomcatはそれを復号化する方法を知る必要があるため、問題を別の場所に移動するだけです。
実際の問題は、Tomcatを除いて誰がserver.xml
にアクセスできるのかということです。解決策は、server.xml
への読み取りアクセスをrootユーザーのみに許可することです。Tomcatはroot権限で起動する必要があります。
それ以外の場合は、起動するたびにパスワードを手動で入力する必要がありますが、これが実行可能なオプションになることはほとんどありません。
@Ryanが述べたように、このソリューションを実装する前にTomcatの TomcatパスワードFAQ を読んでください。セキュリティではなく、あいまいさを追加するだけです。
@Jerome Delattreの答えは、単純なJDBCデータソースでは機能しますが、データソース構築の一部として接続するより複雑なデータソース(たとえば、Oracle.jdbc.xa.client.OracleXADataSource)では機能しません。
これは、既存のファクトリを呼び出す前にパスワードを変更する代替アプローチです。以下は、基本的なデータソースとAtomikos JTA互換のXAデータソース用のファクトリーの例です。
基本的な例:
public class MyEncryptedPasswordFactory extends BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
throws Exception {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "password");
return super.getObjectInstance(obj, name, context, environment);
} else {
throw new IllegalArgumentException(
"Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
}
}
}
アトミコスの例:
public class MyEncryptedAtomikosPasswordFactory extends EnhancedTomcatAtomikosBeanFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
throws NamingException {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "xaProperties.password");
return super.getObjectInstance(obj, name, context, environment);
} else {
throw new IllegalArgumentException(
"Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
}
}
}
リファレンスのパスワード値の更新:
public class DecryptPasswordUtil {
public static void replacePasswordWithDecrypted(Reference reference, String passwordKey) {
if(reference == null) {
throw new IllegalArgumentException("Reference object must not be null");
}
// Search for password addr and replace with decrypted
for (int i = 0; i < reference.size(); i++) {
RefAddr addr = reference.get(i);
if (passwordKey.equals(addr.getType())) {
if (addr.getContent() == null) {
throw new IllegalArgumentException("Password must not be null for key " + passwordKey);
}
String decrypted = yourDecryptionMethod(addr.getContent().toString());
reference.remove(i);
reference.add(i, new StringRefAddr(passwordKey, decrypted));
break;
}
}
}
}
これらのクラスを含む.jarファイルがTomcatのクラスパスにあると、server.xmlを更新してそれらを使用できます。
<Resource factory="com.mycompany.MyEncryptedPasswordFactory" username="user" password="encryptedPassword" ...other options... />
<Resource factory="com.mycompany.MyEncryptedAtomikosPasswordFactory" type="com.atomikos.jdbc.AtomikosDataSourceBean" xaProperties.user="user" xaProperties.password="encryptedPassword" ...other options... />
注:
WinDPAPIを使用してデータを暗号化および復号化できます
public class MyDataSourceFactory extends DataSourceFactory{
private static WinDPAPI winDPAPI;
protected static final String DATA_SOURCE_FACTORY_PROP_PASSWORD = "password";
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception{
Reference ref = (Reference) obj;
for (int i = 0; i < ref.size(); i++) {
RefAddr ra = ref.get(i);
if (ra.getType().equals(DATA_SOURCE_FACTORY_PROP_PASSWORD)) {
if (ra.getContent() != null && ra.getContent().toString().length() > 0) {
String pwd = getUnprotectedData(ra.getContent().toString());
ref.remove(i);
ref.add(i, new StringRefAddr(DATA_SOURCE_FACTORY_PROP_PASSWORD, pwd));
}
break;
}
}
return super.getObjectInstance(obj, name, nameCtx, environment);
}
}
4時間の作業の後、質問と回答を検索して解決策を得ました。 @Jerome Delattreの回答に基づいた完全なコードは(JNDIデータソース構成を使用)です。
Context.xml
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
カスタムデータソースファクトリ:
package mypackage;
public class MyCustomBasicDataSourceFactory extends org.Apache.Tomcat.dbcp.dbcp.BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
Object o = super.getObjectInstance(obj, name, nameCtx, environment);
if (o != null) {
BasicDataSource ds = (BasicDataSource) o;
if (ds.getPassword() != null && ds.getPassword().length() > 0) {
String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
ds.setPassword(pwd);
}
return ds;
} else {
return null;
}
}
}
データソースBean:
@Bean
public DataSource dataSource() {
DataSource ds = null;
JndiTemplate jndi = new JndiTemplate();
try {
ds = jndi.lookup("Java:comp/env/jdbc/myDataSource", DataSource.class);
} catch (NamingException e) {
log.error("NamingException for Java:comp/env/jdbc/myDataSource", e);
}
return ds;
}