SEC-272: Added groups support to JdbcDaoImpl.

This commit is contained in:
Luke Taylor 2008-01-09 18:06:41 +00:00
parent f983ff204d
commit c77475cda6
3 changed files with 133 additions and 24 deletions

View File

@ -32,54 +32,85 @@ import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery; import org.springframework.jdbc.object.MappingSqlQuery;
import org.springframework.util.Assert;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import javax.sql.DataSource; import javax.sql.DataSource;
/** /**
* <p>Retrieves user details (username, password, enabled flag, and authorities) from a JDBC location.</p> * Retrieves user details (username, password, enabled flag, and authorities) from a JDBC location.
* <p>A default database structure is assumed, (see {@link #DEF_USERS_BY_USERNAME_QUERY} and {@link * <p>
* A default database structure is assumed, (see {@link #DEF_USERS_BY_USERNAME_QUERY} and {@link
* #DEF_AUTHORITIES_BY_USERNAME_QUERY}, which most users of this class will need to override, if using an existing * #DEF_AUTHORITIES_BY_USERNAME_QUERY}, which most users of this class will need to override, if using an existing
* scheme. This may be done by setting the default query strings used. If this does not provide enough flexibility, * scheme. This may be done by setting the default query strings used. If this does not provide enough flexibility,
* another strategy would be to subclass this class and override the {@link MappingSqlQuery} instances used, via the * another strategy would be to subclass this class and override the {@link MappingSqlQuery} instances used, via the
* {@link #initMappingSqlQueries()} extension point.</p> * {@link #initMappingSqlQueries()} extension point.
* <p>In order to minimise backward compatibility issues, this DAO does not recognise the expiration of user * <p>
* In order to minimise backward compatibility issues, this DAO does not recognise the expiration of user
* accounts or the expiration of user credentials. However, it does recognise and honour the user enabled/disabled * accounts or the expiration of user credentials. However, it does recognise and honour the user enabled/disabled
* column.</p> * column.
* <p>
* Support for group-based authorities can be enabled by setting the <tt>enableGroups</tt> property to <tt>true</tt>
* (you may also then wish to set <tt>enableAuthorities</tt> to <tt>false</tt> to disable loading of authorities
* directly). With this approach, authorities are allocated to groups and a user's authorities are determined based
* on the groups they are a member of. The net result is the same (a UserDetails containing a set of
* <tt>GrantedAuthority</tt>s is loaded), but the different persistence strategy may be more suitable for the
* administration of some applications.
*
* *
* @author Ben Alex * @author Ben Alex
* @author colin sampaleanu * @author colin sampaleanu
* @author Luke Taylor
* @version $Id$ * @version $Id$
*/ */
public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService { public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
//~ Static fields/initializers ===================================================================================== //~ Static fields/initializers =====================================================================================
public static final String DEF_USERS_BY_USERNAME_QUERY = public static final String DEF_USERS_BY_USERNAME_QUERY =
"SELECT username,password,enabled FROM users WHERE username = ?"; "SELECT username,password,enabled " +
"FROM users " +
"WHERE username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"SELECT username,authority FROM authorities WHERE username = ?"; "SELECT username,authority " +
"FROM authorities " +
"WHERE username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"SELECT g.id, g.group_name, ga.authority " +
"FROM groups g, group_members gm, group_authorities ga " +
"WHERE gm.username = ? " +
"AND g.id = ga.group_id " +
"AND g.id = gm.group_id";
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
protected MappingSqlQuery authoritiesByUsernameMapping; protected MappingSqlQuery authoritiesByUsernameMapping;
protected MappingSqlQuery groupAuthoritiesByUsernameMapping;
protected MappingSqlQuery usersByUsernameMapping; protected MappingSqlQuery usersByUsernameMapping;
private String authoritiesByUsernameQuery; private String authoritiesByUsernameQuery;
private String rolePrefix = ""; private String groupAuthoritiesByUsernameQuery;
private String usersByUsernameQuery; private String usersByUsernameQuery;
private String rolePrefix = "";
private boolean usernameBasedPrimaryKey = true; private boolean usernameBasedPrimaryKey = true;
private boolean enableAuthorities = true;
private boolean enableGroups;
//~ Constructors =================================================================================================== //~ Constructors ===================================================================================================
public JdbcDaoImpl() { public JdbcDaoImpl() {
usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY; usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY; authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
groupAuthoritiesByUsernameQuery = DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY;
} }
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
@ -107,6 +138,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
} }
protected void initDao() throws ApplicationContextException { protected void initDao() throws ApplicationContextException {
Assert.isTrue(enableAuthorities || enableGroups, "Use of either authorities or groups must be enabled");
initMappingSqlQueries(); initMappingSqlQueries();
} }
@ -116,14 +148,14 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
protected void initMappingSqlQueries() { protected void initMappingSqlQueries() {
this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource()); this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource());
this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource()); this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());
this.groupAuthoritiesByUsernameMapping = new GroupAuthoritiesByUsernameMapping(getDataSource());
} }
public boolean isUsernameBasedPrimaryKey() { public boolean isUsernameBasedPrimaryKey() {
return usernameBasedPrimaryKey; return usernameBasedPrimaryKey;
} }
public UserDetails loadUserByUsername(String username) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
throws UsernameNotFoundException, DataAccessException {
List users = usersByUsernameMapping.execute(username); List users = usersByUsernameMapping.execute(username);
if (users.size() == 0) { if (users.size() == 0) {
@ -133,7 +165,17 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[] UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[]
List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername()); Set dbAuthsSet = new HashSet();
if (enableAuthorities) {
dbAuthsSet.addAll(authoritiesByUsernameMapping.execute(user.getUsername()));
}
if (enableGroups) {
dbAuthsSet.addAll(groupAuthoritiesByUsernameMapping.execute(user.getUsername()));
}
List dbAuths = new ArrayList(dbAuthsSet);
addCustomAuthorities(user.getUsername(), dbAuths); addCustomAuthorities(user.getUsername(), dbAuths);
@ -166,10 +208,22 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
authoritiesByUsernameQuery = queryString; authoritiesByUsernameQuery = queryString;
} }
/**
* Allows the default query string used to retrieve group authorities based on username to be overriden, if
* default table or column names need to be changed. The default query is {@link
* #DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped
* back to the same column names as in the default query.
*
* @param queryString The query string to set
*/
public void setGroupAuthoritiesByUsernameQuery(String queryString) {
groupAuthoritiesByUsernameQuery = queryString;
}
/** /**
* Allows a default role prefix to be specified. If this is set to a non-empty value, then it is * Allows a default role prefix to be specified. If this is set to a non-empty value, then it is
* automatically prepended to any roles read in from the db. This may for example be used to add the * automatically prepended to any roles read in from the db. This may for example be used to add the
* <code>ROLE_</code> prefix expected to exist in role names (by default) by some other Spring Security * <tt>ROLE_</tt> prefix expected to exist in role names (by default) by some other Spring Security
* classes, in the case that the prefix is not already present in the db. * classes, in the case that the prefix is not already present in the db.
* *
* @param rolePrefix the new prefix * @param rolePrefix the new prefix
@ -206,6 +260,29 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
this.usersByUsernameQuery = usersByUsernameQueryString; this.usersByUsernameQuery = usersByUsernameQueryString;
} }
protected boolean getEnableAuthorities() {
return enableAuthorities;
}
/**
* Enables loading of authorities (roles) from the authorities table. Defaults to true
*/
public void setEnableAuthorities(boolean enableAuthorities) {
this.enableAuthorities = enableAuthorities;
}
protected boolean getEnableGroups() {
return enableGroups;
}
/**
* Enables support for group authorities. Defaults to false
* @param enableGroups
*/
public void setEnableGroups(boolean enableGroups) {
this.enableGroups = enableGroups;
}
//~ Inner Classes ================================================================================================== //~ Inner Classes ==================================================================================================
/** /**
@ -218,8 +295,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
compile(); compile();
} }
protected Object mapRow(ResultSet rs, int rownum) protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
throws SQLException {
String roleName = rolePrefix + rs.getString(2); String roleName = rolePrefix + rs.getString(2);
GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName); GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);
@ -227,6 +303,21 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
} }
} }
protected class GroupAuthoritiesByUsernameMapping extends MappingSqlQuery {
protected GroupAuthoritiesByUsernameMapping(DataSource ds) {
super(ds, groupAuthoritiesByUsernameQuery);
declareParameter(new SqlParameter(Types.VARCHAR));
compile();
}
protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
String roleName = rolePrefix + rs.getString(3);
GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);
return authority;
}
}
/** /**
* Query object to look up a user. * Query object to look up a user.
*/ */
@ -237,8 +328,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
compile(); compile();
} }
protected Object mapRow(ResultSet rs, int rownum) protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
throws SQLException {
String username = rs.getString(1); String username = rs.getString(1);
String password = rs.getString(2); String password = rs.getString(2);
boolean enabled = rs.getBoolean(3); boolean enabled = rs.getBoolean(3);

View File

@ -113,6 +113,8 @@ public class PopulatedDatabase {
template.execute("INSERT INTO GROUPS VALUES (0, 'GROUP_ZERO')"); template.execute("INSERT INTO GROUPS VALUES (0, 'GROUP_ZERO')");
template.execute("INSERT INTO GROUPS VALUES (1, 'GROUP_ONE')"); template.execute("INSERT INTO GROUPS VALUES (1, 'GROUP_ONE')");
template.execute("INSERT INTO GROUPS VALUES (2, 'GROUP_TWO')"); template.execute("INSERT INTO GROUPS VALUES (2, 'GROUP_TWO')");
// Group 3 isn't used
template.execute("INSERT INTO GROUPS VALUES (3, 'GROUP_THREE')");
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (0, 'ROLE_A')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (0, 'ROLE_A')");
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (1, 'ROLE_B')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (1, 'ROLE_B')");
@ -120,6 +122,9 @@ public class PopulatedDatabase {
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (2, 'ROLE_A')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (2, 'ROLE_A')");
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (2, 'ROLE_B')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (2, 'ROLE_B')");
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (2, 'ROLE_C')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (2, 'ROLE_C')");
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (3, 'ROLE_D')");
template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (3, 'ROLE_E')");
template.execute("INSERT INTO GROUP_MEMBERS VALUES (0, 'jerry', 0)"); template.execute("INSERT INTO GROUP_MEMBERS VALUES (0, 'jerry', 0)");
template.execute("INSERT INTO GROUP_MEMBERS VALUES (1, 'jerry', 1)"); template.execute("INSERT INTO GROUP_MEMBERS VALUES (1, 'jerry', 1)");

View File

@ -79,16 +79,14 @@ public class JdbcDaoImplTests extends TestCase {
assertTrue(authorities.contains("ROLE_SUPERVISOR")); assertTrue(authorities.contains("ROLE_SUPERVISOR"));
} }
public void testCheckDaoOnlyReturnsGrantedAuthoritiesGrantedToUser() public void testCheckDaoOnlyReturnsGrantedAuthoritiesGrantedToUser() throws Exception {
throws Exception {
JdbcDaoImpl dao = makePopulatedJdbcDao(); JdbcDaoImpl dao = makePopulatedJdbcDao();
UserDetails user = dao.loadUserByUsername("scott"); UserDetails user = dao.loadUserByUsername("scott");
assertEquals("ROLE_TELLER", user.getAuthorities()[0].getAuthority()); assertEquals("ROLE_TELLER", user.getAuthorities()[0].getAuthority());
assertEquals(1, user.getAuthorities().length); assertEquals(1, user.getAuthorities().length);
} }
public void testCheckDaoReturnsCorrectDisabledProperty() public void testCheckDaoReturnsCorrectDisabledProperty() throws Exception {
throws Exception {
JdbcDaoImpl dao = makePopulatedJdbcDao(); JdbcDaoImpl dao = makePopulatedJdbcDao();
UserDetails user = dao.loadUserByUsername("peter"); UserDetails user = dao.loadUserByUsername("peter");
assertTrue(!user.isEnabled()); assertTrue(!user.isEnabled());
@ -103,8 +101,7 @@ public class JdbcDaoImplTests extends TestCase {
assertEquals("SELECT USERS FROM FOO", dao.getUsersByUsernameQuery()); assertEquals("SELECT USERS FROM FOO", dao.getUsersByUsernameQuery());
} }
public void testLookupFailsIfUserHasNoGrantedAuthorities() public void testLookupFailsIfUserHasNoGrantedAuthorities() throws Exception {
throws Exception {
JdbcDaoImpl dao = makePopulatedJdbcDao(); JdbcDaoImpl dao = makePopulatedJdbcDao();
try { try {
@ -147,6 +144,24 @@ public class JdbcDaoImplTests extends TestCase {
assertTrue(authorities.contains("ARBITRARY_PREFIX_ROLE_SUPERVISOR")); assertTrue(authorities.contains("ARBITRARY_PREFIX_ROLE_SUPERVISOR"));
} }
public void testGroupAuthoritiesAreLoadedCorrectly() throws Exception {
JdbcDaoImpl dao = makePopulatedJdbcDao();
dao.setEnableAuthorities(false);
dao.setEnableGroups(true);
UserDetails jerry = dao.loadUserByUsername("jerry");
assertEquals(3, jerry.getAuthorities().length);
}
public void testDuplicateGroupAuthoritiesAreRemoved() throws Exception {
JdbcDaoImpl dao = makePopulatedJdbcDao();
dao.setEnableAuthorities(false);
dao.setEnableGroups(true);
// Tom has roles A, B, C and B, C duplicates
UserDetails tom = dao.loadUserByUsername("tom");
assertEquals(3, tom.getAuthorities().length);
}
public void testStartupFailsIfDataSourceNotSet() throws Exception { public void testStartupFailsIfDataSourceNotSet() throws Exception {
JdbcDaoImpl dao = new JdbcDaoImpl(); JdbcDaoImpl dao = new JdbcDaoImpl();
@ -173,8 +188,7 @@ public class JdbcDaoImplTests extends TestCase {
//~ Inner Classes ================================================================================================== //~ Inner Classes ==================================================================================================
private class MockMappingSqlQuery extends MappingSqlQuery { private class MockMappingSqlQuery extends MappingSqlQuery {
protected Object mapRow(ResultSet arg0, int arg1) protected Object mapRow(ResultSet arg0, int arg1) throws SQLException {
throws SQLException {
return null; return null;
} }
} }