Home > Java - J2EE > Apache Shiro JDBC Realm

Apache Shiro JDBC Realm

In my previous post I wrote something about Apache Shiro. Now I want to give a small example of how you create a JDBC realm, which makes both Authentication and Authorization.

public class CustomSecurityRealm extends JdbcRealm {

	/**
	 * The default query used to retrieve account data for the user.
	 */
	protected static final String DEFAULT_AUTHENTICATION_QUERY = "SELECT pass FROM user WHERE username = ?";

	/**
	 * The default query used to retrieve the roles that apply to a user.
	 */
	protected static final String DEFAULT_USER_ROLES_QUERY = 
			// from user self
			"SELECT r.role_name FROM user_roles ur "
			+ "JOIN user u USING(user_id) "
			+ "JOIN roles r USING(role_id) "
			+ "WHERE u.username = ? = ? "
			+ "UNION "
			// from his groups
			+ "SELECT r.role_name FROM user_roles_groups urg "
			+ "JOIN user u ON(urg.user_id=u.user_id) "
			+ "JOIN roles_groups_roles rgr USING(roles_group_id) "
			+ "JOIN roles r ON(rgr.role_id = r.role_id) "
			+ "WHERE u.username = ? ";

    protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;

    protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;

    protected boolean permissionsLookupEnabled = false;	

    private static final Logger log = LoggerFactory.getLogger(CustomSecurityRealm.class);

	/**
	 * jndiDataSourceName
	 */
	protected String jndiDataSourceName;

	public CustomSecurityRealm() {
		super();
	}

	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);
		}
	}

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();

        // Null username is invalid
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }

        Connection conn = null;
        AuthenticationInfo info = null;
        try {
            conn = dataSource.getConnection();

            String password = getPasswordForUser(conn, username);

            if (password == null) {
                throw new UnknownAccountException("No account found for user [" + username + "]");
            }

            info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());

        } catch (SQLException e) {
            final String message = "There was a SQL error while authenticating user [" + username + "]";
            log.error(message, e);

            // Rethrow any SQL errors as an authentication exception
            throw new AuthenticationException(message, e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }

        return info;
    }	

	private String getPasswordForUser(Connection conn, String username)
			throws SQLException {

		PreparedStatement ps = null;
		ResultSet rs = null;
		String password = null;
		try {
			ps = conn.prepareStatement(authenticationQuery);
			ps.setString(1, username);

			// Execute query
			rs = ps.executeQuery();

			// Loop over results - although we are only expecting one result,
			// since usernames should be unique
			boolean foundResult = false;
			while (rs.next()) {

				// Check to ensure only one row is processed
				if (foundResult) {
					throw new AuthenticationException(
							"More than one user row found for user ["
									+ username + "]. Usernames must be unique.");
				}

				password = rs.getString(1);

				foundResult = true;
			}
		} finally {
			JdbcUtils.closeResultSet(rs);
			JdbcUtils.closeStatement(ps);
		}

		return password;
	}

    protected Set getRoleNamesForUser(Connection conn, String username) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        Set roleNames = new LinkedHashSet();

        try {
            ps = conn.prepareStatement(userRolesQuery);
            ps.setString(1, username);
            ps.setString(2, username);

            // Execute query
            rs = ps.executeQuery();

            // Loop over results and add each returned role to a set
            while (rs.next()) {

                String roleName = rs.getString(1);

                // Add the role to the list of names if it isn't null
                if (roleName != null) {
                    roleNames.add(roleName);
                } else {
                    if (log.isWarnEnabled()) {
                        log.warn("Null role name found while retrieving role names for user [" + username + "]");
                    }
                }
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }

        return roleNames;
    }	

}

 

This requires some settings in shiro.ini

 

[main]
# realms to be used
customSecurityRealm=com.pinateknoloji.security.CustomSecurityRealm
customSecurityRealm.jndiDataSourceName=java:app/jdbc/mysql-ds

# specify login page
authc.loginUrl = /login.xhtml?faces-redirect=true
# redirect after successful login
# authc.successUrl  = /restricted/dashboard.xhtml?faces-redirect=true

[urls]

# enable certificateFilter filter for all application pages
/rest/** = authcBasic
/restricted/** = authc

Advertisements
  1. Carlos
    24. November 2012 at 16:24

    Good. This is what i need. Thanks.

  2. 24. May 2013 at 11:17

    This article just has partly code, what about config files of web.xml ..etc. For beginners, it is important to show step by step.

  3. ABdurrahman
    24. May 2013 at 14:00

    Merhaba
    customSecurityRealm=com.xxx.security.CustomSecurityRealm
    customSecurityRealm.jndiDataSourceName=EFA
    seklinde tanımlanmıs satırlar shiro.ini dosyasında olmasına ragmen
    Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms seklinde hata almaktayım ne ile ilgili olabilir ? Yardımcı olabilirmisiniz ?

    • 24. May 2013 at 14:18

      EFA yerine java:app/jdbc/EFA deneyebilirsiniz. Buraya JNDI ısmını yazmalısınız.

    • ABdurrahman
      28. May 2013 at 00:33

      Mehmet bey cevap için teşekkürler, bende belirttiğiniz konu ile ilgili bir sorun oldugunu dusunuyordum fakat web-inf altından .ini dosyasını classpathe tasımam
      ve web.xml de bu konfigurasyon için gerekli satırları eklemem yeterli oldu .

      ShiroFilter
      org.apache.shiro.web.servlet.IniShiroFilter

      configPath
      classpath:Shiro.ini

      Kusura bakmayın türkçe yazdım soruyu . İngilizce yazılması daha uygun ise bundan sonra o şekilde yazabilriim iyi gunler

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: