1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.userdetails.jdbc;
17
18 import java.sql.ResultSet;
19 import java.sql.SQLException;
20 import java.sql.Types;
21 import java.util.List;
22
23 import javax.sql.DataSource;
24
25 import org.acegisecurity.GrantedAuthority;
26 import org.acegisecurity.GrantedAuthorityImpl;
27 import org.acegisecurity.userdetails.User;
28 import org.acegisecurity.userdetails.UserDetails;
29 import org.acegisecurity.userdetails.UserDetailsService;
30 import org.acegisecurity.userdetails.UsernameNotFoundException;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.springframework.context.ApplicationContextException;
34 import org.springframework.dao.DataAccessException;
35 import org.springframework.jdbc.core.SqlParameter;
36 import org.springframework.jdbc.core.support.JdbcDaoSupport;
37 import org.springframework.jdbc.object.MappingSqlQuery;
38
39
40 /***
41 * <p>
42 * Retrieves user details (username, password, enabled flag, and authorities)
43 * from a JDBC location.
44 * </p>
45 *
46 * <p>
47 * A default database structure is assumed, (see {@link
48 * #DEF_USERS_BY_USERNAME_QUERY} and {@link
49 * #DEF_AUTHORITIES_BY_USERNAME_QUERY}, which most users of this class will
50 * need to override, if using an existing scheme. This may be done by setting
51 * the default query strings used. If this does not provide enough
52 * flexibility, another strategy would be to subclass this class and override
53 * the {@link MappingSqlQuery} instances used, via the {@link
54 * #initMappingSqlQueries()} extension point.
55 * </p>
56 *
57 * <p>
58 * In order to minimise backward compatibility issues, this DAO does not
59 * recognise the expiration of user accounts or the expiration of user
60 * credentials. However, it does recognise and honour the user
61 * enabled/disabled column.
62 * </p>
63 *
64 * @author Ben Alex
65 * @author colin sampaleanu
66 * @version $Id: JdbcDaoImpl.java,v 1.17 2005/12/04 10:48:33 benalex Exp $
67 */
68 public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
69
70
71 public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT username,password,enabled FROM users WHERE username = ?";
72 public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT username,authority FROM authorities WHERE username = ?";
73 private static final Log logger = LogFactory.getLog(JdbcDaoImpl.class);
74
75
76
77 protected MappingSqlQuery authoritiesByUsernameMapping;
78 protected MappingSqlQuery usersByUsernameMapping;
79 private String authoritiesByUsernameQuery;
80 private String rolePrefix = "";
81 private String usersByUsernameQuery;
82 private boolean usernameBasedPrimaryKey = true;
83
84
85
86 public JdbcDaoImpl() {
87 usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
88 authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
89 }
90
91
92
93 /***
94 * Allows the default query string used to retrieve authorities based on
95 * username to be overriden, if default table or column names need to be
96 * changed. The default query is {@link
97 * #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure
98 * that all returned columns are mapped back to the same column names as
99 * in the default query.
100 *
101 * @param queryString The query string to set
102 */
103 public void setAuthoritiesByUsernameQuery(String queryString) {
104 authoritiesByUsernameQuery = queryString;
105 }
106
107 public String getAuthoritiesByUsernameQuery() {
108 return authoritiesByUsernameQuery;
109 }
110
111 /***
112 * Allows a default role prefix to be specified. If this is set to a
113 * non-empty value, then it is automatically prepended to any roles read
114 * in from the db. This may for example be used to add the
115 * <code>ROLE_</code> prefix expected to exist in role names (by default)
116 * by some other Acegi Security framework classes, in the case that the
117 * prefix is not already present in the db.
118 *
119 * @param rolePrefix the new prefix
120 */
121 public void setRolePrefix(String rolePrefix) {
122 this.rolePrefix = rolePrefix;
123 }
124
125 public String getRolePrefix() {
126 return rolePrefix;
127 }
128
129 /***
130 * If <code>true</code> (the default), indicates the {@link
131 * #getUsersByUsernameMapping()} returns a username in response to a
132 * query. If <code>false</code>, indicates that a primary key is used
133 * instead. If set to <code>true</code>, the class will use the
134 * database-derived username in the returned <code>UserDetails</code>. If
135 * <code>false</code>, the class will use the {@link
136 * #loadUserByUsername(String)} derived username in the returned
137 * <code>UserDetails</code>.
138 *
139 * @param usernameBasedPrimaryKey <code>true</code> if the mapping queries
140 * return the username <code>String</code>, or <code>false</code>
141 * if the mapping returns a database primary key.
142 */
143 public void setUsernameBasedPrimaryKey(boolean usernameBasedPrimaryKey) {
144 this.usernameBasedPrimaryKey = usernameBasedPrimaryKey;
145 }
146
147 public boolean isUsernameBasedPrimaryKey() {
148 return usernameBasedPrimaryKey;
149 }
150
151 /***
152 * Allows the default query string used to retrieve users based on username
153 * to be overriden, if default table or column names need to be changed.
154 * The default query is {@link #DEF_USERS_BY_USERNAME_QUERY}; when
155 * modifying this query, ensure that all returned columns are mapped back
156 * to the same column names as in the default query. If the 'enabled'
157 * column does not exist in the source db, a permanent true value for this
158 * column may be returned by using a query similar to <br>
159 * <pre>
160 * "SELECT username,password,'true' as enabled FROM users WHERE username = ?"
161 * </pre>
162 *
163 * @param usersByUsernameQueryString The query string to set
164 */
165 public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
166 this.usersByUsernameQuery = usersByUsernameQueryString;
167 }
168
169 public String getUsersByUsernameQuery() {
170 return usersByUsernameQuery;
171 }
172
173 public UserDetails loadUserByUsername(String username)
174 throws UsernameNotFoundException, DataAccessException {
175 List users = usersByUsernameMapping.execute(username);
176
177 if (users.size() == 0) {
178 throw new UsernameNotFoundException("User not found");
179 }
180
181 UserDetails user = (UserDetails) users.get(0);
182
183 List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername());
184
185 if (dbAuths.size() == 0) {
186 throw new UsernameNotFoundException("User has no GrantedAuthority");
187 }
188
189 GrantedAuthority[] arrayAuths = {};
190
191 addCustomAuthorities(user.getUsername(), dbAuths);
192
193 arrayAuths = (GrantedAuthority[]) dbAuths.toArray(arrayAuths);
194
195 String returnUsername = user.getUsername();
196
197 if (!usernameBasedPrimaryKey) {
198 returnUsername = username;
199 }
200
201 return new User(returnUsername, user.getPassword(), user.isEnabled(),
202 true, true, true, arrayAuths);
203 }
204
205 /***
206 * Allows subclasses to add their own granted authorities to the list to be
207 * returned in the <code>User</code>.
208 *
209 * @param username the username, for use by finder methods
210 * @param authorities the current granted authorities, as populated from
211 * the <code>authoritiesByUsername</code> mapping
212 */
213 protected void addCustomAuthorities(String username, List authorities) {}
214
215 protected void initDao() throws ApplicationContextException {
216 initMappingSqlQueries();
217 }
218
219 /***
220 * Extension point to allow other MappingSqlQuery objects to be substituted
221 * in a subclass
222 */
223 protected void initMappingSqlQueries() {
224 this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource());
225 this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());
226 }
227
228
229
230 /***
231 * Query object to look up a user's authorities.
232 */
233 protected class AuthoritiesByUsernameMapping extends MappingSqlQuery {
234 protected AuthoritiesByUsernameMapping(DataSource ds) {
235 super(ds, authoritiesByUsernameQuery);
236 declareParameter(new SqlParameter(Types.VARCHAR));
237 compile();
238 }
239
240 protected Object mapRow(ResultSet rs, int rownum)
241 throws SQLException {
242 String roleName = rolePrefix + rs.getString(2);
243 GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);
244
245 return authority;
246 }
247 }
248
249 /***
250 * Query object to look up a user.
251 */
252 protected class UsersByUsernameMapping extends MappingSqlQuery {
253 protected UsersByUsernameMapping(DataSource ds) {
254 super(ds, usersByUsernameQuery);
255 declareParameter(new SqlParameter(Types.VARCHAR));
256 compile();
257 }
258
259 protected Object mapRow(ResultSet rs, int rownum)
260 throws SQLException {
261 String username = rs.getString(1);
262 String password = rs.getString(2);
263 boolean enabled = rs.getBoolean(3);
264 UserDetails user = new User(username, password, enabled, true,
265 true, true,
266 new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
267
268 return user;
269 }
270 }
271 }