Coverage Report - org.apache.shiro.authc.AbstractAuthenticator
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractAuthenticator
80%
36/45
72%
13/18
2.556
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.shiro.authc;
 20  
 
 21  
 import org.apache.shiro.subject.PrincipalCollection;
 22  
 import org.slf4j.Logger;
 23  
 import org.slf4j.LoggerFactory;
 24  
 
 25  
 import java.util.ArrayList;
 26  
 import java.util.Collection;
 27  
 
 28  
 
 29  
 /**
 30  
  * Superclass for almost all {@link Authenticator} implementations that performs the common work around authentication
 31  
  * attempts.
 32  
  * <p/>
 33  
  * This class delegates the actual authentication attempt to subclasses but supports notification for
 34  
  * successful and failed logins as well as logouts. Notification is sent to one or more registered
 35  
  * {@link AuthenticationListener AuthenticationListener}s to allow for custom processing logic
 36  
  * when these conditions occur.
 37  
  * <p/>
 38  
  * In most cases, the only thing a subclass needs to do (via its {@link #doAuthenticate} implementation)
 39  
  * is perform the actual principal/credential verification process for the submitted {@code AuthenticationToken}.
 40  
  *
 41  
  * @since 0.1
 42  
  */
 43  
 public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
 44  
 
 45  
     /*-------------------------------------------
 46  
     |             C O N S T A N T S             |
 47  
     ============================================*/
 48  
     /**
 49  
      * Private class log instance.
 50  
      */
 51  1
     private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticator.class);
 52  
 
 53  
     /*-------------------------------------------
 54  
     |    I N S T A N C E   V A R I A B L E S    |
 55  
     ============================================*/
 56  
     /**
 57  
      * Any registered listeners that wish to know about things during the authentication process.
 58  
      */
 59  
     private Collection<AuthenticationListener> listeners;
 60  
 
 61  
     /*-------------------------------------------
 62  
     |         C O N S T R U C T O R S           |
 63  
     ============================================*/
 64  
 
 65  
     /**
 66  
      * Default no-argument constructor. Ensures the internal
 67  
      * {@link AuthenticationListener AuthenticationListener} collection is a non-null {@code ArrayList}.
 68  
      */
 69  55
     public AbstractAuthenticator() {
 70  55
         listeners = new ArrayList<AuthenticationListener>();
 71  55
     }
 72  
 
 73  
     /*--------------------------------------------
 74  
     |  A C C E S S O R S / M O D I F I E R S    |
 75  
     ============================================*/
 76  
 
 77  
     /**
 78  
      * Sets the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
 79  
      * attempts.
 80  
      *
 81  
      * @param listeners one or more {@code AuthenticationListener}s that should be notified due to an
 82  
      *                  authentication attempt.
 83  
      */
 84  
     @SuppressWarnings({"UnusedDeclaration"})
 85  
     public void setAuthenticationListeners(Collection<AuthenticationListener> listeners) {
 86  0
         if (listeners == null) {
 87  0
             this.listeners = new ArrayList<AuthenticationListener>();
 88  
         } else {
 89  0
             this.listeners = listeners;
 90  
         }
 91  0
     }
 92  
 
 93  
     /**
 94  
      * Returns the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
 95  
      * attempts.
 96  
      *
 97  
      * @return the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
 98  
      *         attempts.
 99  
      */
 100  
     @SuppressWarnings({"UnusedDeclaration"})
 101  
     public Collection<AuthenticationListener> getAuthenticationListeners() {
 102  2
         return this.listeners;
 103  
     }
 104  
 
 105  
     /*-------------------------------------------
 106  
     |               M E T H O D S               |
 107  
     ============================================*/
 108  
 
 109  
     /**
 110  
      * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
 111  
      * authentication was successful for the specified {@code token} which resulted in the specified
 112  
      * {@code info}.  This implementation merely iterates over the internal {@code listeners} collection and
 113  
      * calls {@link AuthenticationListener#onSuccess(AuthenticationToken, AuthenticationInfo) onSuccess}
 114  
      * for each.
 115  
      *
 116  
      * @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication.
 117  
      * @param info  the returned {@code AuthenticationInfo} resulting from the successful authentication.
 118  
      */
 119  
     protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) {
 120  19
         for (AuthenticationListener listener : this.listeners) {
 121  1
             listener.onSuccess(token, info);
 122  
         }
 123  19
     }
 124  
 
 125  
     /**
 126  
      * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
 127  
      * authentication failed for the
 128  
      * specified {@code token} which resulted in the specified {@code ae} exception.  This implementation merely
 129  
      * iterates over the internal {@code listeners} collection and calls
 130  
      * {@link AuthenticationListener#onFailure(AuthenticationToken, AuthenticationException) onFailure}
 131  
      * for each.
 132  
      *
 133  
      * @param token the submitted {@code AuthenticationToken} that resulted in a failed authentication.
 134  
      * @param ae    the resulting {@code AuthenticationException} that caused the authentication to fail.
 135  
      */
 136  
     protected void notifyFailure(AuthenticationToken token, AuthenticationException ae) {
 137  7
         for (AuthenticationListener listener : this.listeners) {
 138  1
             listener.onFailure(token, ae);
 139  
         }
 140  7
     }
 141  
 
 142  
     /**
 143  
      * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that a
 144  
      * {@code Subject} has logged-out.  This implementation merely
 145  
      * iterates over the internal {@code listeners} collection and calls
 146  
      * {@link AuthenticationListener#onLogout(org.apache.shiro.subject.PrincipalCollection) onLogout}
 147  
      * for each.
 148  
      *
 149  
      * @param principals the identifying principals of the {@code Subject}/account logging out.
 150  
      */
 151  
     protected void notifyLogout(PrincipalCollection principals) {
 152  9
         for (AuthenticationListener listener : this.listeners) {
 153  0
             listener.onLogout(principals);
 154  
         }
 155  9
     }
 156  
 
 157  
     /**
 158  
      * This implementation merely calls
 159  
      * {@link #notifyLogout(org.apache.shiro.subject.PrincipalCollection) notifyLogout} to allow any registered listeners
 160  
      * to react to the logout.
 161  
      *
 162  
      * @param principals the identifying principals of the {@code Subject}/account logging out.
 163  
      */
 164  
     public void onLogout(PrincipalCollection principals) {
 165  9
         notifyLogout(principals);
 166  9
     }
 167  
 
 168  
     /**
 169  
      * Implementation of the {@link Authenticator} interface that functions in the following manner:
 170  
      * <ol>
 171  
      * <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
 172  
      * authentication behavior.</li>
 173  
      * <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
 174  
      * {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
 175  
      * {@link AuthenticationListener AuthenticationListener}s of the exception and then propogate the exception
 176  
      * for the caller to handle.</li>
 177  
      * <li>If no exception is thrown (indicating a successful login),
 178  
      * {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
 179  
      * {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
 180  
      * <li>Return the {@code AuthenticationInfo}</li>
 181  
      * </ol>
 182  
      *
 183  
      * @param token the submitted token representing the subject's (user's) login principals and credentials.
 184  
      * @return the AuthenticationInfo referencing the authenticated user's account data.
 185  
      * @throws AuthenticationException if there is any problem during the authentication process - see the
 186  
      *                                 interface's JavaDoc for a more detailed explanation.
 187  
      */
 188  
     public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
 189  
 
 190  27
         if (token == null) {
 191  1
             throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
 192  
         }
 193  
 
 194  26
         log.trace("Authentication attempt received for token [{}]", token);
 195  
 
 196  
         AuthenticationInfo info;
 197  
         try {
 198  26
             info = doAuthenticate(token);
 199  20
             if (info == null) {
 200  1
                 String msg = "No account information found for authentication token [" + token + "] by this " +
 201  
                         "Authenticator instance.  Please check that it is configured correctly.";
 202  1
                 throw new AuthenticationException(msg);
 203  
             }
 204  7
         } catch (Throwable t) {
 205  7
             AuthenticationException ae = null;
 206  7
             if (t instanceof AuthenticationException) {
 207  6
                 ae = (AuthenticationException) t;
 208  
             }
 209  7
             if (ae == null) {
 210  
                 //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
 211  
                 //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
 212  1
                 String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
 213  
                         "error? (Typical or expected login exceptions should extend from AuthenticationException).";
 214  1
                 ae = new AuthenticationException(msg, t);
 215  
             }
 216  
             try {
 217  7
                 notifyFailure(token, ae);
 218  0
             } catch (Throwable t2) {
 219  0
                 if (log.isWarnEnabled()) {
 220  0
                     String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
 221  
                             "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
 222  
                             "and propagating original AuthenticationException instead...";
 223  0
                     log.warn(msg, t2);
 224  
                 }
 225  7
             }
 226  
 
 227  
 
 228  7
             throw ae;
 229  19
         }
 230  
 
 231  19
         log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
 232  
 
 233  19
         notifySuccess(token, info);
 234  
 
 235  19
         return info;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Template design pattern hook for subclasses to implement specific authentication behavior.
 240  
      * <p/>
 241  
      * Common behavior for most authentication attempts is encapsulated in the
 242  
      * {@link #authenticate} method and that method invokes this one for custom behavior.
 243  
      * <p/>
 244  
      * <b>N.B.</b> Subclasses <em>should</em> throw some kind of
 245  
      * {@code AuthenticationException} if there is a problem during
 246  
      * authentication instead of returning {@code null}.  A {@code null} return value indicates
 247  
      * a configuration or programming error, since {@code AuthenticationException}s should
 248  
      * indicate any expected problem (such as an unknown account or username, or invalid password, etc).
 249  
      *
 250  
      * @param token the authentication token encapsulating the user's login information.
 251  
      * @return an {@code AuthenticationInfo} object encapsulating the user's account information
 252  
      *         important to Shiro.
 253  
      * @throws AuthenticationException if there is a problem logging in the user.
 254  
      */
 255  
     protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
 256  
             throws AuthenticationException;
 257  
 
 258  
 
 259  
 }