001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.shiro.realm;
020    
021    import org.slf4j.Logger;
022    import org.slf4j.LoggerFactory;
023    
024    import org.apache.shiro.authc.AuthenticationException;
025    import org.apache.shiro.authc.AuthenticationInfo;
026    import org.apache.shiro.authc.AuthenticationToken;
027    import org.apache.shiro.authc.IncorrectCredentialsException;
028    import org.apache.shiro.authc.LogoutAware;
029    import org.apache.shiro.authc.UsernamePasswordToken;
030    import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
031    import org.apache.shiro.authc.credential.CredentialsMatcher;
032    import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
033    import org.apache.shiro.cache.CacheManager;
034    import org.apache.shiro.subject.PrincipalCollection;
035    
036    
037    /**
038     * A top-level abstract implementation of the <tt>Realm</tt> interface that only implements authentication support
039     * (log-in) operations and leaves authorization (access control) behavior to subclasses.
040     *
041     * <p>Since a Realm provides both authentication <em>and</em> authorization operations, the implementation approach for
042     * this class could have been reversed.  That is, authorization support could have been implemented here and
043     * authentication support left to subclasses.
044     *
045     * <p>The reason the existing implementation is in place though
046     * (authentication support) is that most authentication operations are fairly common across the large majority of
047     * applications, whereas authorization operations are more so heavily dependent upon the application's data model, which
048     * can vary widely.
049     *
050     * <p>By providing the most common authentication operations here and leaving data-model specific authorization checks
051     * to subclasses, a top-level abstract class for most common authentication behavior is more useful as an extension
052     * point for most applications.
053     *
054     * @author Les Hazlewood
055     * @author Jeremy Haile
056     * @since 0.2
057     */
058    public abstract class AuthenticatingRealm extends CachingRealm implements LogoutAware {
059    
060        //TODO - complete JavaDoc
061    
062        private static final Logger log = LoggerFactory.getLogger(AuthenticatingRealm.class);
063    
064        /**
065         * Password matcher used to determine if the provided password matches
066         * the password stored in the data store.
067         */
068        private CredentialsMatcher credentialsMatcher = new SimpleCredentialsMatcher();
069    
070        /**
071         * The class that this realm supports for authentication tokens.  This is used by the
072         * default implementation of the {@link Realm#supports(org.apache.shiro.authc.AuthenticationToken)} method to
073         * determine whether or not the given authentication token is supported by this realm.
074         */
075        private Class<? extends AuthenticationToken> authenticationTokenClass = UsernamePasswordToken.class;
076    
077        /*--------------------------------------------
078        |         C O N S T R U C T O R S           |
079        ============================================*/
080        public AuthenticatingRealm() {
081        }
082    
083        public AuthenticatingRealm(CacheManager cacheManager) {
084            setCacheManager(cacheManager);
085        }
086    
087        public AuthenticatingRealm(CredentialsMatcher matcher) {
088            setCredentialsMatcher(matcher);
089        }
090    
091        public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
092            setCacheManager(cacheManager);
093            setCredentialsMatcher(matcher);
094        }
095    
096        /*--------------------------------------------
097        |  A C C E S S O R S / M O D I F I E R S    |
098        ============================================*/
099        /**
100         * Returns the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted
101         * credentials with those stored in the system.
102         *
103         * <p>Unless overridden by the {@link #setCredentialsMatcher setCredentialsMatcher} method, the default
104         * value is a {@link org.apache.shiro.authc.credential.SimpleCredentialsMatcher SimpleCredentialsMatcher} instance.
105         *
106         * @return the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted
107         *         credentials with those stored in the system.
108         */
109        public CredentialsMatcher getCredentialsMatcher() {
110            return credentialsMatcher;
111        }
112    
113        /**
114         * Sets the CrendialsMatcher used during an authentication attempt to verify submitted credentials with those
115         * stored in the system.  The implementation of this matcher can be switched via configuration to
116         * support any number of schemes, including plain text comparisons, hashing comparisons, and others.
117         *
118         * <p>Unless overridden by this method, the default value is a
119         * {@link org.apache.shiro.authc.credential.SimpleCredentialsMatcher} instance.
120         *
121         * @param credentialsMatcher the matcher to use.
122         */
123        public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
124            this.credentialsMatcher = credentialsMatcher;
125        }
126    
127        /**
128         * Returns the authenticationToken class supported by this realm.
129         *
130         * <p>The default value is <tt>{@link org.apache.shiro.authc.UsernamePasswordToken UsernamePasswordToken.class}</tt>, since
131         * about 90% of realms use username/password authentication, regardless of their protocol (e.g. over jdbc, ldap,
132         * kerberos, http, etc).
133         *
134         * <p>If subclasses haven't already overridden the {@link Realm#supports Realm.supports(AuthenticationToken)} method,
135         * they must {@link #setAuthenticationTokenClass(Class) set a new class} if they won't support
136         * <tt>UsernamePasswordToken</tt> authentication token submissions.
137         *
138         * @return the authenticationToken class supported by this realm.
139         * @see #setAuthenticationTokenClass
140         */
141        public Class getAuthenticationTokenClass() {
142            return authenticationTokenClass;
143        }
144    
145        /**
146         * Sets the authenticationToken class supported by this realm.
147         *
148         * <p>Unless overridden by this method, the default value is
149         * {@link org.apache.shiro.authc.UsernamePasswordToken UsernamePasswordToken.class} to support the majority of applications.
150         *
151         * @param authenticationTokenClass the class of authentication token instances supported by this realm.
152         * @see #getAuthenticationTokenClass getAuthenticationTokenClass() for more explanation.
153         */
154        public void setAuthenticationTokenClass(Class<? extends AuthenticationToken> authenticationTokenClass) {
155            this.authenticationTokenClass = authenticationTokenClass;
156        }
157    
158        /*--------------------------------------------
159        |               M E T H O D S               |
160        ============================================*/
161        /**
162         * Convenience implementation that returns
163         * <tt>getAuthenticationTokenClass().isAssignableFrom( token.getClass() );</tt>.  Can be overridden
164         * by subclasses for more complex token checking.
165         * <p>Most configurations will only need to set a different class via
166         * {@link #setAuthenticationTokenClass}, as opposed to overriding this method.
167         *
168         * @param token the token being submitted for authentication.
169         * @return true if this authentication realm can process the submitted token instance of the class, false otherwise.
170         */
171        public boolean supports(AuthenticationToken token) {
172            return token != null && getAuthenticationTokenClass().isAssignableFrom(token.getClass());
173        }
174    
175        public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
176    
177            AuthenticationInfo info = doGetAuthenticationInfo(token);
178    
179            if (info == null) {
180                if (log.isDebugEnabled()) {
181                    String msg = "No authentication information found for submitted authentication token [" + token + "].  " +
182                            "Returning null.";
183                    log.debug(msg);
184                }
185                return null;
186            }
187    
188            CredentialsMatcher cm = getCredentialsMatcher();
189            if (cm != null) {
190                if (!cm.doCredentialsMatch(token, info)) {
191                    String msg = "The credentials provided for account [" + token +
192                            "] did not match the expected credentials.";
193                    throw new IncorrectCredentialsException(msg);
194                }
195            } else {
196                throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
197                        "credentials during authentication.  If you do not wish for credentials to be examined, you " +
198                        "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
199            }
200    
201            return info;
202        }
203    
204        /**
205         * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
206         * authentication token.
207         *
208         * <p>For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
209         * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
210         * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
211         *
212         * <p>A <tt>null</tt> return value means that no account could be associated with the specified token.
213         *
214         * @param token the authentication token containing the user's principal and credentials.
215         * @return an {@link AuthenticationInfo} object containing account data resulting from the
216         *         authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
217         * @throws org.apache.shiro.authc.AuthenticationException
218         *          if there is an error acquiring data or performing
219         *          realm-specific authentication logic for the specified <tt>token</tt>
220         */
221        protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
222    
223        /**
224         * Default implementation that does nothing (no-op) and exists as a convenience mechanism in case subclasses
225         * wish to override it to implement realm-specific logout logic for the given user account logging out.</p>
226         * <p/>
227         * In a single-realm Shiro configuration (most applications), the <code>principals</code> method
228         * argument will be the same as that which is contained in the <code>AuthenticationInfo</code> object returned by the
229         * {@link #doGetAuthenticationInfo} method (that is, {@link AuthenticationInfo#getPrincipals info.getPrincipals()}).
230         * <p/>
231         * In a multi-realm Shiro configuration, the given <code>principals</code> method
232         * argument could contain principals returned by many realms.  Therefore the subclass implementation would need
233         * to know how to extract the principal(s) relevant to only itself and ignore other realms' principals.  This is
234         * usually done by calling {@link org.apache.shiro.subject.PrincipalCollection#fromRealm(String) principals.fromRealm(name)},
235         * using the realm's own {@link Realm#getName() name}.
236         *
237         * @param principals the application-specific Subject/user identifier that is logging out.
238         */
239        public void onLogout(PrincipalCollection principals) {
240            //no-op, here for subclass override if desired.
241        }
242    
243    }