可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
The resource definition in tomcat's server.xml
looks something like this...
<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"/>
The password is in the clear. How to avoid this?
回答1:
As said before encrypting passwords is just moving the problem somewhere else.
Anyway, it's quite simple.
Just write a class with static fields for your secret key and so on, and static methods to encrypt, decrypt your passwords.
Encrypt your password in Tomcat's configuration file (server.xml
or yourapp.xml
...) using this class.
And to decrypt the password "on the fly" in Tomcat, extend the DBCP's BasicDataSourceFactory
and use this factory in your resource.
It will look like:
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
And for the custom factory:
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;
}
}
Hope this helps.
回答2:
Tomcat has a Password FAQ that specifically addresses your question. In short: Keep the password in the clear and properly lock-down your server.
That page also offers some suggestions of how security-by-obscurity might be used to pass an auditor's checklist.
回答3:
Tomcat needs to know how to connect to the database, so it needs access to the plain text password. If the password in encrypted, Tomcat needs to know how to decrypt it, so you are only moving the problem somewhere else.
The real problem is: who can access server.xml
except for Tomcat? A solution is to give read access to server.xml
only to root user, requiring that Tomcat is started with root privileges: if a malicious user gains root privileges on the system, losing a database password is probably a minor concern.
Otherwise you should type the password manually at every startup, but this is seldom a viable option.
回答4:
As @Ryan mentioned, please read Tomcat's Tomcat Password FAQ before implementing this solution. You are only adding obscurity not security.
@Jerome Delattre's answer will work for simple JDBC data sources, but not for more complicated ones that connect as part of the datasource construction (e.g. oracle.jdbc.xa.client.OracleXADataSource).
This is alternative approach that modifies the password prior to calling the existing factory. Below is an example of a factory for a basic datasource and one for an Atomikos JTA compatible XA datasource.
Basic Example:
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 Example:
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());
}
}
}
Updating password value in Reference:
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;
}
}
}
}
Once the .jar file containing these classes are in Tomcat's classpath you can update your server.xml to use them.
<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... />
回答5:
After 4 hours of work, search questions and answers I got the solution.
Based on the answer by @Jerome Delattre here is the complete code (with the JNDI Data source configuration).
Context.xml
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
Custom Data Source Factory:
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;
}
}
}
Data source 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;
}
回答6:
Note:
You can use WinDPAPI to encrypt and decrypt data
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);
}
}
回答7:
All of the foregoing having been said, if you still want to avoid plain text passwords you can use a hashing algorithm such as SHA-256 or (preferably) SHA-512. When a password is created, obtain the hashed value and store it rather than the password. When a user logs in, hash the password and see of it matches the stored hashed password.
Hashing algorithms take a character string (or number) from a small string (or number) space into a much larger one in a way that is expensive to reverse.
回答8:
We use C#'s 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;
});
)