View Javadoc

    1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
    2    *
    3    * Licensed under the Apache License, Version 2.0 (the "License");
    4    * you may not use this file except in compliance with the License.
    5    * You may obtain a copy of the License at
    6    *
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    *
    9    * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    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      //~ Static fields/initializers =============================================
    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      //~ Instance fields ========================================================
    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      //~ Constructors ===========================================================
    85
    86      public JdbcDaoImpl() {
    87          usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
    88          authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
    89      }
    90
    91      //~ Methods ================================================================
    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); // contains no GrantedAuthority[]
    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     //~ Inner Classes ==========================================================
    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 }