如何避免在tomcat的server.xml中以明文形式存储密码?DataSource的资源定义?

时间:2008-09-24 19:03:16

标签: tomcat jdbc configuration

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"/>

密码是明文。怎么避免这个?

9 个答案:

答案 0 :(得分:39)

如前所述,加密密码只会将问题转移到其他地方。

无论如何,这很简单。 只需为您的密钥等编写一个包含静态字段的类,然后使用静态方法加密,解密您的密码。 使用此类在Tomcat的配置文件(server.xmlyourapp.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;
    }
}

希望这有帮助。

答案 1 :(得分:9)

Tomcat有a Password FAQ专门解决您的问题。简而言之:保持密码清晰并正确锁定服务器。

该页面还提供了一些关于如何使用默示安全性来通过审核员清单的建议。

答案 2 :(得分:3)

Tomcat需要知道如何连接数据库,因此需要访问纯文本密码。如果密码是加密的,Tomcat需要知道如何解密它,所以你只是把问题转移到其他地方。

真正的问题是:除了Tomcat之外,谁可以访问server.xml?解决方案是仅向root用户提供对server.xml的读访问权,要求以root权限启动Tomcat:如果恶意用户在系统上获得root权限,则丢失数据库密码可能是一个小问题。

否则,您应该在每次启动时手动输入密码,但这很少是可行的选择。

答案 3 :(得分:3)

正如@Ryan所说,请在实施此解决方案之前阅读Tomcat的Tomcat Password FAQ。你只是添加了默默无闻的安全性。

@Jerome Delattre的答案适用于简单的JDBC数据源,但不适用于作为数据源构造的一部分连接的更复杂的数据源(例如oracle.jdbc.xa.client.OracleXADataSource)。

这是在调用现有工厂之前修改密码的替代方法。下面是一个基本数据源工厂和一个Atomikos JTA兼容XA数据源的工厂示例。

基本示例:

Application:abc.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.InvalidCastException

Atomikos示例:

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());
        }
    }
}

一旦包含这些类的.jar文件位于Tomcat的类路径中,您就可以更新server.xml以使用它们。

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;
            }
        }
    }
}

答案 4 :(得分:2)

经过4个小时的工作,搜索问题和答案我得到了解决方案。 根据@Jerome Delattre的回答,这里是完整的代码(使用JNDI数据源配置)。

context.xml中

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
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;
}

数据源bean:

Sub ConditionalReplace()
    ' 10 Jan 2018

    Dim Pos As Long
    Dim n As Integer

    With Selection
        .HomeKey Unit:=wdStory
        .Find.Text = "---- "

        Do While .Find.Execute = True
            Pos = .Start + 1
            .Collapse wdCollapseStart
            .MoveStart wdWord, -1
            If (StrComp(Trim(.Text), "a", vbTextCompare) = 0) Or _
               (StrComp(Trim(.Text), "an", vbTextCompare) = 0) Then
               .Delete
               n = n + 1
            End If
            .SetRange Pos, ActiveDocument.Characters.Count
        Loop
        .HomeKey Unit:=wdStory
    End With
    MsgBox n & " replacements were made."
End Sub

答案 5 :(得分:2)

注意:

您可以使用 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);
  }
}

答案 6 :(得分:0)

问题:如上所述,在context.xml中加密凭据同时将解密密钥存储在下一个文件中实际上只是解决了问题。由于访问context.xml的用户还需要访问解密密钥,因此,如果应用程序或OS用户受到威胁,则所有凭据仍将受到威胁。

解决方案:唯一可以提高安全性的解决方案是,可以从整个设置中完全删除解密密钥的解决方案。这可以通过要求有人在启动时在应用程序中键入密码来实现,然后将其用于解密所有凭据。

进一步推迟解决方案:在大多数情况下,许多管理员和/或开发人员都可能需要知道这样的密码。通过使用允许共享密码(例如1Password)的密码共享解决方案,然后将安全性分配给每个管理员/开发人员的用于解锁其个人密码库的个人主密码。

解决方案/讽刺行为的可能降级:使用此设置,最坏的情况是有人将主密码仅保留在监视器附带的便笺上。这是否比在解密值旁边的文件中包含解密密钥更安全,这可能应该是一个单独的SO问题,或者可能是未来的研究。

答案 7 :(得分:-2)

如上所述,如果您仍想避免使用纯文本密码,可以使用散列算法,如SHA-256或(最好)SHA-512。创建密码时,获取散列值并存储它而不是密码。当用户登录时,哈希密码并查看它与存储的哈希密码匹配。 散列算法将一个字符串(或数字)从一个小字符串(或数字)空间转换为一个更大的字符串(或数字),反转成本很高。

答案 8 :(得分:-8)

我们使用C#的SHA1CryptoServiceProvider

print(SHA1CryptoServiceProvider sHA1Hasher = new SHA1CryptoServiceProvider();
        ASCIIEncoding enc = new ASCIIEncoding();

        byte[] arrbytHashValue = sHA1Hasher.ComputeHash(enc.GetBytes(clearTextPW));
        string HashData = System.BitConverter.ToString(arrbytHashValue);
        HashData = HashData.Replace("-", "");
        if (HashData == databaseHashedPassWO)
        {
            return true;
        }
        else
        {
            return false;
        });