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.
Two questions:
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.
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:
<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.
The password entered by user is concatenated with a random generated salt as well as a static salt. The concatenated string is passed as the input of hashing function. The result obtained is stored in database. Dynamic salt is required to be stored in the database since it is different for different users.
Good password management guidelines require that a password never be stored in plaintext. The following code reads a password from a properties file and uses the password to connect to a database. ...
The passwords are stored in the relational database. To keep it simple in this example we send the user credentials with every HTTP request. It means the application must start authentication whenever the client wants to access the API. First, we create an API we want to protect with Spring Security:
If Spring Security finds the header, it starts the authentication. To authenticate, Spring Security needs user data with user names and password hashes. That’s why we have to implement the UserDetailsService interface. This interface loads user-specific data and needs read-only access to user data:
The PasswordEncoderFactories sets BCryptPasswordEncoder as the default encoder. Now, when user data is saved during registration, the password encoder will encode the password and add a prefix at the beginning of the result string. The encoded password looks like this: {bcrypt}$2a$10$4V9kA793Pi2xf94dYFgKWuw8ukyETxWb7tZ4/mfco9sWkwvBQndxW
i recently blogged about this:
you can tell tomcat's jdbcrealm to use a digest algorithm on the password like sha-256 and save the hash rather than plaintext passwords.
Suppose your User entities look like this:
@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:
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.
<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:
+----------------------+ +-------------------+
| 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:
+----------------------+ +-------------------+ +--------------------+
| 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 View using SQL which had the expected format and draws it's data from my webapps user data:
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:
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:
some links:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With