41

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

密码是明文的。如何避免这种情况?

4

9 回答 9

43

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

无论如何,这很简单。只需为您的密钥等编写一个包含静态字段的类,以及加密、解密密码的静态方法。使用此类在 Tomcat 的配置文件(server.xml或...)中加密您的密码。yourapp.xml

并且要在 Tomcat 中“即时”解密密码,扩展 DBCPBasicDataSourceFactory并在您的资源中使用这个工厂。

它看起来像:

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

希望这可以帮助。

于 2008-12-10T11:28:55.523 回答
11

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

该页面还提供了一些关于如何使用隐匿安全性来通过审核员检查表的建议。

于 2015-06-15T23:39:43.827 回答
4

正如@Ryan 提到的,请在实施此解决方案之前阅读 Tomcat 的Tomcat 密码常见问题解答。您只是增加了默默无闻而不是安全性。

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

Atomikos 示例:

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... />
于 2016-09-02T18:44:58.280 回答
3

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

真正的问题是:server.xml除了Tomcat,谁可以访问?一个解决方案是只授予 root 用户读取权限server.xml,要求 Tomcat 以 root 权限启动:如果恶意用户获得系统的 root 权限,丢失数据库密码可能是一个小问题。

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

于 2008-09-26T15:49:07.280 回答
2

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

上下文.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;
}
于 2018-01-10T03:30:58.997 回答
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);
  }
}
于 2018-05-04T04:16:52.763 回答
0

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

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

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

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

于 2020-07-22T15:27:35.760 回答
-3

前面已经说过,如果您仍然想避免使用纯文本密码,您可以使用散列算法,例如 SHA-256 或(最好)SHA-512。创建密码时,获取哈希值并存储它而不是密码。当用户登录时,对密码进行哈希处理并查看它是否与存储的哈希密码匹配。散列算法将一个字符串(或数字)从一个小字符串(或数字)空间转换为一个更大的空间,这种方式的反转成本很高。

于 2016-09-03T00:23:18.513 回答
-9

我们使用 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;
        });

)

于 2008-09-24T19:19:46.647 回答