One of the authentication options you get in Glassfish is to store your users/roles just in database tables. This way, you can implement your custom UIs and logic for managing users. The usual solution you’ll find over the web for that is to create them by hand with SQL and fill using JDBC. What I needed was to get some entities for the users so I just could start my app and everything is created by JPA. This is not that easy, as Glassfish has some assumptions about the tables you use (as it’s also using plain JDBC to retrieve users from the database). Here’s how I’ve managed to do it using JPA 2.0 entities.
Entities
Entities have to be declared obeying some rules:
- User name column has to be a
VARCHAR
and password column has to be a VARCHAR(32)
. Also, it is best to keep the password in a char[]
array and not in String
so you have full control over when it’s being destroyed and that it’s not being kept in memory for too long (it’s not very secure in this code, but you’ll find some guide on the web for sure). - User to role mapping has to be done in a separate database table with (at least) two
VARCHAR
s.
So here is the entities code I finally got.
User
package com.wordpress.jdevel.meetingpoint.model.security;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import com.wordpress.jdevel.meetingpoint.model.security.Role.ROLE;
@Entity(name = "USERS")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "USER_NAME")
private String userName;
@Column(name = "PASSWD", length = 32,
columnDefinition = "VARCHAR(32)")
private char[] password;
@OneToOne(fetch = FetchType.EAGER,
cascade = CascadeType.ALL, mappedBy = "user")
private Role role;
protected User() {
}
public User(String userName, char[] password, ROLE role) {
this.userName = userName;
this.password = hashPassword(password);
this.role = new Role(role, this);
}
public char[] getPassword() {
return password;
}
public void setPassword(char[] password) {
this.password = hashPassword(password);
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
role.setUser(this);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final User other = (User) obj;
if ((this.userName == null) ? (other.userName != null) :
!this.userName.equals(other.userName)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 83 * hash + (this.userName != null ? this.userName.hashCode() : 0);
return hash;
}
@Override
public String toString() {
return "com.wordpress.jdevel.meetingpoint.model.User[name=" +
userName + "]";
}
private char[] hashPassword(char[] password) {
char[] encoded = null;
try {
ByteBuffer passwdBuffer =
Charset.defaultCharset().encode(CharBuffer.wrap(password));
byte[] passwdBytes = passwdBuffer.array();
MessageDigest mdEnc = MessageDigest.getInstance("MD5");
mdEnc.update(passwdBytes, 0, password.length);
encoded = new BigInteger(1, mdEnc.digest()).toString(16).toCharArray();
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(User.class.getName()).log(Level.SEVERE, null, ex);
}
return encoded;
}
}
Remember to leave VARCHAR(32)
on the password. It won’t work with what you get by default from JPA.
In this code, your password is encrypted with an MD5 hash, changing it to SHA1 shouldn’t be hard (it should be enough to modify MessageDigest.getInstance("MD5");
). You pass it as a char[]
array with what the user entered and it’s internally stored as a hash.
User -> Role Mapping
package com.wordpress.jdevel.meetingpoint.model.security;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
@Entity(name = "ROLES")
public class Role implements Serializable {
public static enum ROLE {
ADMINISTRATOR, USER, GUEST
}
private static final long serialVersionUID = 1L;
@Id
@Column(name = "ROLE_NAME")
@Enumerated(EnumType.STRING)
private ROLE role;
@Id
@OneToOne
@JoinColumn(name = "USER_NAME")
private User user;
protected Role() {
}
protected Role(ROLE role, User user) {
this.role = role;
this.user = user;
}
public ROLE getRole() {
return role;
}
public void setRole(ROLE role) {
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Role other = (Role) obj;
if (this.role != other.role) {
return false;
}
if (this.user != other.user && (this.user == null ||
!this.user.equals(other.user))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 89 * hash + (this.role != null ? this.role.hashCode() : 0);
hash = 89 * hash + (this.user != null ? this.user.hashCode() : 0);
return hash;
}
@Override
public String toString() {
return "com.wordpress.jdevel.meetingpoint.model.security.Group[name=" +
role + "]";
}
}
That should be all from the code side. Rest is the Glassfish configuration.
Glassfish Configuration
First, you need to declare a new JDBC security realm:
A small description for what’s in the table:
JAAS Context | Just leave it as jdbcRealm; it has to be just this text. It took me quite some time to figure out why it was not working, and it was because I’d put something different there. |
JNDI | It’s a JNDI name for your application data source (from JDBC Resources). |
Rest is quite self explanatory. If not, just leave it as is, it will work with the entities I’ve posted. What’s not in the table is a user column name in the Group table. It has to be exactly the same as the User Name column you declared a few fields before (it’s also in the entity).
Now save it and configure your application to use it.
Configure Application
Now what you have to do is edit glassfish-application.xml (or sun-application.xml, whichever you have) and add authentication realm and roles:
="1.0"="UTF-8"
<!DOCTYPE glassfish-application PUBLIC
"-//GlassFish.org//DTD GlassFish Application Server 3.1 Java EE Application 6.0//EN"
"http://glassfish.org/dtds/glassfish-application_6_0-1.dtd">
<glassfish-application>
<realm>MeetingPoint_JDBC</realm>
<security-role-mapping>
<security-role-mapping>
<role-name>ADMINISTRATOR</role-name>
<group-name>ADMINISTRATOR</group-name>
</security-role-mapping>
</security-role-mapping>
<security-role-mapping>
<role-name>CREATOR</role-name>
<group-name>CREATOR</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>USER</role-name>
<group-name>USER</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>GUEST</role-name>
<group-name>GUEST</group-name>
</security-role-mapping>
</glassfish-application>
And now, you can create some users using these entities and JPA 2.0, for example using your JSF2.0 page, and they should be able to authenticate right away.