no

How to use Shiro with JDBC on JavaEE and Glassfish

Before you proceed with this tutorial, it's best to from this link to know how to setup a JavaEE6 project with Shiro integrated: http:/...

Before you proceed with this tutorial, it's best to from this link to know how to setup a JavaEE6 project with Shiro integrated:
http://czetsuya-tech.blogspot.com/2012/10/how-to-integrate-apache-shiro-with.html

From there we will focus on the changes:
1.) Update shiro.ini

[main]
saltedJdbcRealm=com.ctr.mdl.commons.web.security.shiro.JdbcRealmImpl

# any object property is automatically configurable in Shiro.ini file
saltedJdbcRealm.jndiDataSourceName=dummyDS 

# the realm should handle also authorization
saltedJdbcRealm.permissionsLookupEnabled=true

# If not filled, subclasses of JdbcRealm assume "select password from users where username = ?"
# first result column is password, second result column is salt 
saltedJdbcRealm.authenticationQuery = SELECT password, salt FROM crm_users WHERE username = ?

# If not filled, subclasses of JdbcRealm assume "select role_name from user_roles where username = ?"
saltedJdbcRealm.userRolesQuery = SELECT name FROM crm_roles a INNER JOIN crm_user_roles b ON a.id=b.role_id INNER JOIN crm_users c ON c.id=b.user_id WHERE c.username = ?

# If not filled, subclasses of JdbcRealm assume "select permission from roles_permissions where role_name = ?"
saltedJdbcRealm.permissionsQuery = SELECT action FROM crm_permissions WHERE role = ?

# password hashing specification, put something big for hasIterations
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256
sha256Matcher.hashIterations=1
saltedJdbcRealm.credentialsMatcher = $sha256Matcher

cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager 
cacheManager.cacheManagerConfigFile=classpath:ehcache.xml
securityManager.cacheManager=$cacheManager 

shiro.loginUrl = /login.xhtml

[urls]
/login.xhtml = authc
/logout = logout

Things to take note:
1.) We should extend JdbcRealm to implement a salted password.
2.) Create a datasource in Glassfish named: dummyDS. For this project, I've use postgresql.
3.) I've enabled permission lookup.
4.) I've enabled ehcache, by adding dependency to pom.xml:
<dependency>
 <groupId>org.apache.shiro</groupId>
 <artifactId>shiro-ehcache</artifactId>
 <version>1.2.1</version>
</dependency>

New/Updated classes:
JdbcRealmImpl:
package com.ctr.mdl.commons.web.security.shiro;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.JdbcUtils;
import org.apache.shiro.util.SimpleByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Edward P. Legaspi
 * @since Oct 15, 2012
 */
public class JdbcRealmImpl extends JdbcRealm {
 private static final Logger log = LoggerFactory
   .getLogger(JdbcRealmImpl.class);

 protected String jndiDataSourceName;

 public JdbcRealmImpl() {

 }

 public String getJndiDataSourceName() {
  return jndiDataSourceName;
 }

 public void setJndiDataSourceName(String jndiDataSourceName) {
  this.jndiDataSourceName = jndiDataSourceName;
  this.dataSource = getDataSourceFromJNDI(jndiDataSourceName);
 }

 private DataSource getDataSourceFromJNDI(String jndiDataSourceName) {
  try {
   InitialContext ic = new InitialContext();
   return (DataSource) ic.lookup(jndiDataSourceName);
  } catch (NamingException e) {
   log.error("JNDI error while retrieving " + jndiDataSourceName, e);
   throw new AuthorizationException(e);
  }
 }

 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(
   AuthenticationToken token) throws AuthenticationException {
  // identify account to log to
  UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
  String username = userPassToken.getUsername();

  if (username == null) {
   log.debug("Username is null.");
   return null;
  }

  // read password hash and salt from db
  PasswdSalt passwdSalt = getPasswordForUser(username);

  if (passwdSalt == null) {
   log.debug("No account found for user [" + username + "]");
   return null;
  }

  // return salted credentials
  SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,
    passwdSalt.password, getName());
  info.setCredentialsSalt(new SimpleByteSource(passwdSalt.salt));

  return info;
 }

 private PasswdSalt getPasswordForUser(String username) {
  PreparedStatement statement = null;
  ResultSet resultSet = null;
  Connection conn = null;
  try {
   conn = dataSource.getConnection();
   statement = conn.prepareStatement(authenticationQuery);
   statement.setString(1, username);

   resultSet = statement.executeQuery();

   boolean hasAccount = resultSet.next();
   if (!hasAccount)
    return null;

   String salt = null;
   String password = resultSet.getString(1);
   if (resultSet.getMetaData().getColumnCount() > 1)
    salt = resultSet.getString(2);

   if (resultSet.next()) {
    throw new AuthenticationException(
      "More than one user row found for user [" + username
        + "]. Usernames must be unique.");
   }

   return new PasswdSalt(password, salt);
  } catch (SQLException e) {
   final String message = "There was a SQL error while authenticating user ["
     + username + "]";
   if (log.isErrorEnabled()) {
    log.error(message, e);
   }
   throw new AuthenticationException(message, e);

  } finally {
   JdbcUtils.closeResultSet(resultSet);
   JdbcUtils.closeStatement(statement);
   JdbcUtils.closeConnection(conn);
  }
 }

 class PasswdSalt {
  public String password;
  public String salt;

  public PasswdSalt(String password, String salt) {
   super();
   this.password = password;
   this.salt = salt;
  }
 }
}

And the 3 model classes which basically contains:
User.java
@Entity
@Table(name = "CRM_USERS")
@SequenceGenerator(name = "ID_GENERATOR", sequenceName = "CRM_USERS_SEQ")
public class User implements Serializeable { 
        private static final long serialVersionUID = 6142315693769197546L;

 @Id
 @GeneratedValue(generator = "ID_GENERATOR")
 private Long id;

 @Column(name = "USERNAME", length = 50, unique = true)
 private String userName;

 @Column(name = "PASSWORD", length = 250)
 private String password;

 @Column(name = "SALT", length = 100)
 private String salt;

 @ManyToMany(fetch = FetchType.LAZY)
 @JoinTable(name = "CRM_USER_ROLES", joinColumns = @JoinColumn(name = "USER_ID"), inverseJoinColumns = @JoinColumn(name = "ROLE_ID"))
 private List roles = new ArrayList();
}
Role.java
@Entity(name = "com.ctr.mdl.models.user.Role")
@Table(name = "CRM_ROLES")
@SequenceGenerator(name = "ID_GENERATOR", sequenceName = "CRM_ROLES_SEQ")
public class Role implements Serializable {
 private static final long serialVersionUID = 6142315693769197546L;

 @Id
 @GeneratedValue(generator = "ID_GENERATOR")
 private Long id;

 @Column(name = "NAME", nullable = false, length = 50)
 private String name;

 @Column(name = "DESCRIPTION", nullable = false, length = 50)
 private String description;

 @ManyToMany(fetch = FetchType.LAZY)
 @JoinTable(name = "CRM_USER_ROLES", joinColumns = @JoinColumn(name = "ROLE_ID"), inverseJoinColumns = @JoinColumn(name = "USER_ID"))
 private List users = new ArrayList();
}
Permission.java
@Entity
@Table(name = "CRM_PERMISSIONS")
@SequenceGenerator(name = "ID_GENERATOR", sequenceName = "CRM_PERMISSIONS_SEQ")
public class Permission implements Serializeable {
 private static final long serialVersionUID = -2844386098501951453L;

 @Column(name = "ROLE", nullable = false)
 private String role;

 @Column(name = "ACTION", nullable = false, length = 1500)
 private String action;

 public String getRole() {
  return role;
 }
} 

 Reference: http://meri-stuff.blogspot.com/2011/04/apache-shiro-part-2-realms-database-and.html

Related:
Seam Security: http://czetsuya-tech.blogspot.com/2013/06/how-to-setup-seam3-security-in-jboss-7.html

Related

java-library 8453674702535010612

Post a Comment Default Comments

4 comments

Anonymous said...

hi
can we use shiro withour maven , just a simple project with jdbc, jpa toplink and apache tomcat?

czetsuya said...

hi, yes of course maven is a dependency management so it's not really a requirement in building an app that uses shiro.

Anonymous said...

This looks awful similar to this code https://github.com/SomMeri/SimpleShiroSecuredApplication/blame/authentication_stored_in_database/src/main/java/org/meri/simpleshirosecuredapplication/realm/JNDIAndSaltAwareJdbcRealm.java

czetsuya said...

Well of course, didn't you see the reference at the end of the blog? My point is how to setup shiro in JavaEE6.

item