java 没有以纯文本形式存储密码的休眠身份验证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5747136/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 12:34:55  来源:igfitidea点击:

Hibernate authentication without passwords stored in plain text

javasqlhibernatewindows-authentication

提问by KyleM

My goal is to authenticate to the database using a JDBC/Hibernate in a secure manner, without storing passwords in plain text. Code examples appreciated. I'm already using waffle to authenticate the user so if there was some way to use the credentials that waffle obtained from the user, and forward those to the DB, that would be good.

我的目标是以安全的方式使用 JDBC/Hibernate 对数据库进行身份验证,而不是以纯文本形式存储密码。代码示例表示赞赏。我已经在使用 waffle 对用户进行身份验证,因此如果有某种方法可以使用从用户那里获得的 waffle 凭据,并将其转发到数据库,那就太好了。

Two questions:

两个问题:

  1. What is the recommended way to do multi hop authentication (the client, web server, and database are all different machines) with tomcat/hibernate/spring on web server, a sql database, and obviously client browser?
  2. I would also settle for a way to use a single user account to do authentication, as long as that user account's information was not stored in plain text anywhere. The user account will need both read/write privileges on the DB.
  1. 使用 web 服务器上的 tomcat/hibernate/spring、sql 数据库和明显的客户端浏览器进行多跳身份验证(客户端、Web 服务器和数据库都是不同的机器)的推荐方法是什么?
  2. 我还想采用一种使用单个用户帐户进行身份验证的方法,只要该用户帐户的信息没有以纯文本形式存储在任何地方。用户帐户将需要对 DB 的读/写权限。

I found some useful information about connecting to SQL Server in this thread. However, I'm expecting that Tomcat will be running under the default account which is like, Local System or something. As far as I know, that account cannot be used to do windows authentication to the database.

在这个线程中找到了一些关于连接到 SQL Server 的有用信息。但是,我希望 Tomcat 将在默认帐户下运行,例如本地系统或其他帐户。据我所知,该帐户不能用于对数据库进行 Windows 身份验证。

My solution:

我的解决方案

I did end up using the approach mentioned in the above thread. Instead of running the Tomcat service as Local System it is now running as a user. That user has permission to access the database. My hibernate configuration file is configured as follows:

我确实最终使用了上面线程中提到的方法。Tomcat 服务不再作为本地系统运行,而是作为用户运行。该用户有权访问数据库。我的hibernate配置文件配置如下:

    <property name="hibernate.connection.url">
jdbc:sqlserver://system:port;databaseName=myDb;integratedSecurity=true;
</property>

To those who provided responses

致那些提供回复的人

I appreciate everyone's help and I will try out some of the techniques mentioned in the thread. My issue with some of the responses is that they require symmetric encryption which requires a secret key. Keeping the key secret is almost the exact same problem as storing the password in plain text.

我感谢大家的帮助,我将尝试线程中提到的一些技术。我对某些响应的问题是它们需要对称加密,这需要一个密钥。保持密钥秘密与以纯文本形式存储密码几乎完全相同。

采纳答案by Pierre

Okay, let's take a look at the problem. You want to have the authentication information made available but not hardcoded anywhere in code or in file system. What I would suggest:

好,我们来看看问题。您希望身份验证信息可用,但不要在代码或文件系统的任何位置进行硬编码。我的建议是:

  • require the administrator of the application to specify the authentication information upon application startup either via jmx or via a webpage that does not require any database connection.
  • Add a servlet filter to limit access until database authentication information is entered.
  • 要求应用程序的管理员在应用程序启动时通过 jmx 或通过不需要任何数据库连接的网页指定身份验证信息。
  • 添加 servlet 过滤器以限制访问,直到输入数据库身份验证信息。

This solution does require some extending spring context loading so that it waits until the authentication information is specified (via entry page).

此解决方案确实需要一些扩展的 spring 上下文加载,以便它等待直到指定身份验证信息(通过入口页面)。

回答by fasseg

i recently blogged about this:

我最近写了一篇关于这个的博客

you can tell tomcat's jdbcrealm to use a digest algorithm on the password like sha-256and save the hash rather than plaintext passwords.

您可以告诉 tomcat 的 jdbcrealm 在密码上使用摘要算法,例如sha-256并保存散列而不是明文密码。

Suppose your User entities look like this:

假设您的 User 实体如下所示:

@Entity
@Table(name = "cr_users")
public class UserDetails{
    @Id
    @GeneratedValue
    private long id;
    private String name;
    private String passwordHash;
    @ManyToMany
    private Set<Group> groups;
}

when creating a new User via a service it's possible to create a password hash by using a MessageDigest:

通过服务创建新用户时,可以使用MessageDigest创建密码哈希:

public UserDetails createNewUser(String username,String passwd,Set<Group> groups){
       UserDetails u=new UserDetails();
       u.setname(username);
       u.setGroups(groups);
       u.setPassword(createHash(passwd));
       return u;
}
public String createHash(String data){
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        digest.update(password.getBytes());
        byte byteData[] = digest.digest();
        //convert bytes to hex chars
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < byteData.length; i++) {
         sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
        }
        return sb.toString();
}

since SHA-256 will always yield the same hashvalue for the same input you can tell tomcat's JDBCRealm to use this algorithm to verify passwords.

由于 SHA-256 将始终为相同的输入产生相同的哈希值,因此您可以告诉 tomcat 的 JDBCRealm 使用此算法来验证密码。

<Realm className="org.apache.catalina.realm.JDBCRealm"
       driverName="org.postgresql.Driver"
       connectionURL="jdbc:postgresql://localhost:5432/mydb"
       connectionName="myuser" connectionPassword="mypass"
       userTable="tc_realm_users" userNameCol="username" userCredCol="passwordhash" 
       userRoleTable="tc_realm_groups" roleNameCol="groupname"
       digest="sha-256"/>

the problem is that tomcat will expect a distinct format for the usertable like this:

问题是 tomcat 会期望像这样的 usertable 有一个不同的格式:

+----------------------+  +-------------------+
|   tc_realm_users     |  | tc_realm_groups   |
+----------------------+  +-------------------+
| username     varchar |  | username  varchar |
| passwordhash varchar |  | groupname varchar |
+----------------------+  +-------------------+

if your user data model fits you're lucky, but my Hibernate generated tables looked like that:

如果您的用户数据模型适合您,那么您很幸运,但我的 Hibernate 生成的表看起来像这样:

+----------------------+  +-------------------+  +--------------------+
|     cr_users         |  |      cr_groups    |  | cr_users_cr_groups |
+----------------------+  +-------------------+  +--------------------+
| id              long |  | id           long |  | cr_users_id   long |
| name         varchar |  | name      varchar |  | groups_id     long |
| passwordhash varchar |  +-------------------+  +--------------------+
+----------------------+

so i created a Viewusing SQL which had the expected format and draws it's data from my webapps user data:

所以我使用 SQL创建了一个具有预期格式的视图,并从我的 webapps 用户数据中提取它的数据:

create view tc_realm_groups as
select 
  cr_users.name as username,
  groups.name as groupname
from cr_users 
left join (
        select 
                cr_users_cr_groups.cr_users_id,cr_groups.name 
        from cr_groups 
        left join 
                cr_users_cr_groups 
                on cr_users_cr_groups.groups_id=cr_groups.id
) as groups on groups.cr_users_id=id;

create view tc_realm_users as
select 
  name as username
from cr_users;

with that tomcat was able to authenticate/authorize agains my already existing user data and wrote the data in the context so i could use it in my Jersey (JSR-311)resources:

使用该 tomcat 能够再次验证/授权我现有的用户数据并在上下文中写入数据,以便我可以在我的Jersey (JSR-311)资源中使用它:

public Response getEvent(@Context SecurityContext sc,@PathParam("id") long id) {
                log.debug("auth: " + sc.getAuthenticationScheme());
                log.debug("user: " + sc.getUserPrincipal().getName()); // the username!
                log.debug("admin-privileges: " + sc.isUserInRole("webapp-admin"));
                return Response.ok(“auth success”).build();
        }

there are also some other Realm implementations out there:

还有一些其他的 Realm 实现:

  • JDBCRealm
  • DataSourceRealm
  • JNDIRealm
  • UserDatabaseRealm
  • MemoryRealm
  • JAASRealm
  • CombinedRealm
  • LockOutRealm
  • JDBC领域
  • 数据源领域
  • JNDI领域
  • 用户数据库领域
  • 内存领域
  • JAAS领域
  • 联合领域
  • 锁定领域

some links:

一些链接:

回答by ag112

If I understand correctly, your environment is hibernate framework based web app deployed in tomcat.

如果我理解正确,您的环境是部署在 tomcat 中的基于休眠框架的 Web 应用程序。

Now currently you must have configured JDBC passsword i) either in your hibernate configuration file (generally hibernate.cfg.xml file) in property :-

现在,您必须在 hibernate 配置文件(通常是 hibernate.cfg.xml 文件)中的属性中配置 JDBC 密码 i):-

hibernate.connection.password

hibernate.connection.password

ii) or in tomcat configuration file:-

ii) 或在 tomcat 配置文件中:-

<Resource name="jdbc/myoracle" ......password="tiger".../>

Now you wish to NOT store clear password in any of above files.

现在您不希望在上述任何文件中存储明文密码。

In your application code, you must be doing :-

在您的应用程序代码中,您必须执行以下操作:-

Line1: org.hibernate.cfs.Condiguration configuration=new Configraution().configure(<hibernate configuration path>);

then,Line2:  configuration.buildSessionFactory().openSession() to create a hibernate session which has underlying JDBC connection.

1) One way can be basically:- You can have your password encrypted using any java security alogirthm using any JCE provider.You store the encrypted password in any of above configuration files (hibernate or tomcat as per your project environment).

1)一种方法基本上可以是:-您可以使用任何JCE提供程序使用任何java安全算法加密您的密码。您将加密的密码存储在上述任何配置文件(根据您的项目环境的hibernate或tomcat)中。

and then between Line1 and Line2, you can have decryption logic like:-

然后在 Line1 和 Line2 之间,你可以有像这样的解密逻辑:-

Line1: org.hibernate.cfs.Condiguration configuration=new Configraution().configure(<hibernate configuration path>);

String encrpytedPassword=
configuration.getProperty("hibernate.connection.password"); \will return encrypted password

//decryption logic to decypt the encrypted password:-
String decryptedPwd=decrypt(encrpytedPassword);

configuration.setProperty("hibernate.connection.password",decryptedPwd);

then,Line2:  configuration.buildSessionFactory().openSession()

You can make encryption and decryption as complex as you wish for e.g. encryption of reverse-string-of-clear-password. You can use any JCE API:- jasrypt,bouncy castle. You should need some understanding of java cryptography.Please refer to :-

您可以根据需要使加密和解密变得复杂,例如加密反向字符串的明文密码。您可以使用任何 JCE API:- jasrypt,充气城堡。你需要对java密码学有一定的了解。请参考:-

http://download.oracle.com/javase/1.4.2/docs/guide/security/CryptoSpec.html

http://download.oracle.com/javase/1.4.2/docs/guide/security/CryptoSpec.html

2)In case you are concerned about password being transmitting in clear in JDBC connection protocol, then you can use SSL support from DB provider to secure connection. For e.g. to have SSL JDBC connection with your DB server. Refer to your DB server resoruces for this.

2)如果您担心 JDBC 连接协议中的密码传输清晰,那么您可以使用 DB 提供程序的 SSL 支持来保护连接。例如,与您的数据库服务器建立 SSL JDBC 连接。为此,请参阅您的数据库服务器资源。

EDITED TO CLARIFY keylM's comment ON HOW TO ENCRYPT THE JDBC PASSWORD

编辑以澄清 keylM 关于如何加密 JDBC 密码的评论

lets say you have a private and public key pair:= privare.key and public.cer. You can have JDBC password encrypted with private key and save the encrypted password in configuration file. You can use OpenSSL to import public certificate into jks (java keystore) file and have it in your JAVA_HOME\jre\lib\security.

假设您有一个私钥和公钥对:= privare.key 和 public.cer。您可以使用私钥对JDBC 密码进行加密,并将加密后的密码保存在配置文件中。您可以使用 OpenSSL 将公共证书导入 jks(java 密钥库)文件并将其放在您的 JAVA_HOME\jre\lib\security 中。

In your decryption logic:-

在您的解密逻辑中:-

  KeyStore ks = KeyStore.getInstance("JKS");
  ks.load(new FileInputStream("keystore.jks"),<jks password>); //jks password can be hardoded
Certificate cert=      ks.getCertificate(<certificate alias>);
//use certificate to decrypt the encrypted password

So in this scenario:- a hacker would need 3 things in order to capture JDBC password which makes system less vulenrable:- i) encrypted JDBC password ii) JKS store iii) JKS store password

因此,在这种情况下:- 黑客需要 3 样东西才能捕获 JDBC 密码,这使系统不易受到攻击:- i) 加密的 JDBC 密码 ii) JKS 存储 iii) JKS 存储密码

You may ask question of then now how about JKS store password, well whether its a key,passphrase or password, in encryption-decryption system, atleast one thing shuld be highly secure; otherwise it jeopradize the whole system....in above scenario, certificate can be given to each developer machine to let him import into his jks file protected by same jks store password..everybody (developer) would know only JKS store password but never JDBC password...

您现在可能会问,JKS 存储密码如何,无论是密钥、密码还是密码,在加解密系统中,至少有一件事应该是高度安全的;否则它会危及整个系统....在上述情况下,可以向每个开发人员机器提供证书,让他导入受相同 jks 存储密码保护的 jks 文件..每个人(开发人员)只会知道 JKS 存储密码,但永远不会JDBC密码...

回答by Matt Moran

Normally you'd have the app authenticate to the sql database under just one user name, passing the user's details if necessary as data in its queries so that you return data pertinent to just that end user. Have your clients specified that each end user should authenticate to the database as a separate user?

通常,您只需要使用一个用户名就可以让应用程序对 sql 数据库进行身份验证,如有必要,将用户的详细信息作为查询中的数据传递,以便您返回与该最终用户相关的数据。您的客户是否指定每个最终用户都应作为单独的用户对数据库进行身份验证?

回答by andi

To be able to transparently encrypt/decrypt passwords in your database with hibernate you need to integrate something like Jasypt.

为了能够使用 hibernate 透明地加密/解密数据库中的密码,您需要集成诸如 Jasypt 之类的东西。

Homepage: www.jasypt.org See section: Jasypt + Hibernate 3

主页:www.jasypt.org 参见部分:Jasypt + Hibernate 3

Here is how to integrate it:

以下是如何集成它:

  1. Download jasypt.jar and add it to your runtime classpath

  2. I would suggest using a registered encryptor:

  1. 下载 jasypt.jar 并将其添加到您的运行时类路径

  2. 我建议使用注册的加密器:

<typedef name="encrypted" class="org.jasypt.hibernate.type.EncryptedStringType">
  <param name="encryptorRegisteredName">strongHibernateStringEncryptor</param>
</typedef>
<class name="User" table="USER">
  <property name="password" column="PASSWORD" type="encrypted" />
<class>
<typedef name="encrypted" class="org.jasypt.hibernate.type.EncryptedStringType">
  <param name="encryptorRegisteredName">strongHibernateStringEncryptor</param>
</typedef>
<class name="User" table="USER">
  <property name="password" column="PASSWORD" type="encrypted" />
<class>

回答by Kamahire

I got your point KyleM. You can do :

我明白你的意思了 KyleM。你可以做 :

  1. Create plain text file or registry(in case of Windows) place somewhere on other server in encrypted mode.

  2. OR you can use this Lamport's one-time password algorithm

  1. 创建纯文本文件或注册表(在 Windows 的情况下)以加密模式放置在其他服务器上的某个位置。

  2. 或者您可以使用此Lamport 的一次性密码算法

回答by Bohemian

Usually, this is handled using a "sysadmin" approach - using the OS:

通常,这是使用“系统管理员”方法处理的 - 使用操作系统:

The basic concept is "externalising configuration parameters". Passwords are stored in plaintext in a "properties file" (that the web server accesses at runtime). The passwords are protected by restricting access to the files using OS-level file permissions. Typically, only "operations" staff can read/write the file, and the web server needs to run with read-only privileges to the file.

基本概念是“外部化配置参数”。密码以明文形式存储在“属性文件”(Web 服务器在运行时访问)中。通过使用操作系统级别的文件权限限制对文件的访问来保护密码。通常,只有“操作”人员可以读/写文件,并且 Web 服务器需要以对该文件的只读权限运行。

The benefits of this approach are:

这种方法的好处是:

  • Simple to understand and implement (no entering encrypted values)
  • Protected by software designed to protect - it's one of the few things that an OS does (also, encryption can be cracked if the file can be read)
  • Simple to set up dev/test environments - just open up permissions for dev/test. Also, only the production runtime server needs to have proper security
  • Avoids dependencies on "no business value" libraries (that don't help solve your business problem)
  • 易于理解和实施(无需输入加密值)
  • 受旨在保护的软件的保护 - 这是操作系统所做的为数不多的事情之一(此外,如果可以读取文件,则可以破解加密)
  • 设置开发/测试环境很简单——只需打开开发/测试的权限。此外,只有生产运行时服务器需要具有适当的安全性
  • 避免对“无业务价值”库的依赖(无助于解决您的业务问题)

回答by bsimic

You can use a JDNI Datasource on your application server that will have the connection information for the Database.

您可以在应用程序服务器上使用 JDNI 数据源,该数据源将具有数据库的连接信息。

Then you can just tell your application via your web.xml to use the datasource on your web application server.

然后您可以通过 web.xml 告诉您的应用程序使用您的 Web 应用程序服务器上的数据源。

Here is how I did it on a Weblogic 9 using Hibernate 3:

下面是我在 Weblogic 9 上使用 Hibernate 3 的方法:

In Hibernate.cfg.xml

在 Hibernate.cfg.xml 中

<property name="connection.datasource">jdbc/MYJDNINAME</property>  
<property name="connection.autocommit">true</property> 
<property name="hibernate.connection.release_mode">on_close</property>

In weblogic.xml

在 weblogic.xml 中

    <reference-descriptor>
    <resource-description>
        <res-ref-name>jdbc/MYJDNINAME</res-ref-name>
        <jndi-name>MYJDNINAME</jndi-name>
    </resource-description>
</reference-descriptor>

Similar solutions can be used for tomcat and other application servers:

类似的解决方案可用于 tomcat 和其他应用服务器:

Tomcat 6 Instructions

Tomcat 6 使用说明