mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-13 22:03:33 +00:00
SEC-272: Added groups support to JdbcDaoImpl.
This commit is contained in:
parent
f983ff204d
commit
c77475cda6
@ -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);
|
||||||
|
@ -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)");
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user