Coverage Report - org.acegisecurity.providers.jaas.JaasAuthenticationProvider
 
Classes in this File Line Coverage Branch Coverage Complexity
JaasAuthenticationProvider
95% 
100% 
1.87
 
 1  
 /* Copyright 2004, 2005, 2006 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.providers.jaas;
 17  
 
 18  
 import org.acegisecurity.AcegiSecurityException;
 19  
 import org.acegisecurity.Authentication;
 20  
 import org.acegisecurity.AuthenticationException;
 21  
 import org.acegisecurity.GrantedAuthority;
 22  
 
 23  
 import org.acegisecurity.context.HttpSessionContextIntegrationFilter;
 24  
 import org.acegisecurity.context.SecurityContext;
 25  
 
 26  
 import org.acegisecurity.providers.AuthenticationProvider;
 27  
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 28  
 import org.acegisecurity.providers.jaas.event.JaasAuthenticationFailedEvent;
 29  
 import org.acegisecurity.providers.jaas.event.JaasAuthenticationSuccessEvent;
 30  
 
 31  
 import org.acegisecurity.ui.session.HttpSessionDestroyedEvent;
 32  
 
 33  
 import org.apache.commons.logging.Log;
 34  
 import org.apache.commons.logging.LogFactory;
 35  
 
 36  
 import org.springframework.beans.BeansException;
 37  
 import org.springframework.beans.factory.InitializingBean;
 38  
 
 39  
 import org.springframework.context.*;
 40  
 
 41  
 import org.springframework.core.io.Resource;
 42  
 
 43  
 import org.springframework.util.Assert;
 44  
 
 45  
 import java.io.IOException;
 46  
 
 47  
 import java.security.Principal;
 48  
 import java.security.Security;
 49  
 
 50  
 import java.util.Arrays;
 51  
 import java.util.HashSet;
 52  
 import java.util.Iterator;
 53  
 import java.util.Set;
 54  
 
 55  
 import javax.security.auth.callback.Callback;
 56  
 import javax.security.auth.callback.CallbackHandler;
 57  
 import javax.security.auth.callback.UnsupportedCallbackException;
 58  
 import javax.security.auth.login.Configuration;
 59  
 import javax.security.auth.login.LoginContext;
 60  
 import javax.security.auth.login.LoginException;
 61  
 
 62  
 
 63  
 /**
 64  
  * An {@link AuthenticationProvider} implementation that retrieves user details from a JAAS login configuration.
 65  
  *
 66  
  * <p>This <code>AuthenticationProvider</code> is capable of validating {@link
 67  
  * org.acegisecurity.providers.UsernamePasswordAuthenticationToken} requests contain the correct username and
 68  
  * password.</p>
 69  
  * <p>This implementation is backed by a <a
 70  
  * href="http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html">JAAS</a> configuration. The
 71  
  * loginConfig property must be set to a given JAAS configuration file. This setter accepts a Spring {@link
 72  
  * org.springframework.core.io.Resource} instance. It should point to a JAAS configuration file containing an index
 73  
  * matching the {@link #setLoginContextName(java.lang.String) loginContextName} property.
 74  
  * </p>
 75  
  * <p>
 76  
  * For example: If this JaasAuthenticationProvider were configured in a Spring WebApplicationContext the xml to
 77  
  * set the loginConfiguration could be as follows...
 78  
  * <pre>
 79  
  * &lt;property name="loginConfig"&gt;
 80  
  *   &lt;value&gt;/WEB-INF/login.conf&lt;/value&gt;
 81  
  * &lt;/property&gt;
 82  
  * </pre>
 83  
  * </p>
 84  
  * <p>
 85  
  * The loginContextName should coincide with a given index in the loginConfig specifed. The loginConfig file
 86  
  * used in the JUnit tests appears as the following...
 87  
  * <pre> JAASTest {
 88  
  *   org.acegisecurity.providers.jaas.TestLoginModule required;
 89  
  * };
 90  
  * </pre>
 91  
  * Using the example login configuration above, the loginContextName property would be set as <i>JAASTest</i>...
 92  
  * <pre>
 93  
  *  &lt;property name="loginContextName"&gt; &lt;value&gt;JAASTest&lt;/value&gt; &lt;/property&gt;
 94  
  * </pre>
 95  
  * </p>
 96  
  *  <p>When using JAAS login modules as the authentication source, sometimes the
 97  
  * <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/security/auth/login/LoginContext.html">LoginContext</a> will
 98  
  * require <i>CallbackHandler</i>s. The JaasAuthenticationProvider uses an internal
 99  
  * <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/security/auth/callback/CallbackHandler.html">CallbackHandler
 100  
  * </a> to wrap the {@link JaasAuthenticationCallbackHandler}s configured in the ApplicationContext.
 101  
  * When the LoginContext calls the internal CallbackHandler, control is passed to each
 102  
  * {@link JaasAuthenticationCallbackHandler} for each Callback passed.
 103  
  * </p>
 104  
  * <p>{@link JaasAuthenticationCallbackHandler}s are passed to the JaasAuthenticationProvider through the {@link
 105  
  * #setCallbackHandlers(org.acegisecurity.providers.jaas.JaasAuthenticationCallbackHandler[]) callbackHandlers}
 106  
  * property.
 107  
  * <pre>
 108  
  * &lt;property name="callbackHandlers"&gt;
 109  
  *   &lt;list&gt;
 110  
  *     &lt;bean class="org.acegisecurity.providers.jaas.TestCallbackHandler"/&gt;
 111  
  *     &lt;bean class="{@link JaasNameCallbackHandler org.acegisecurity.providers.jaas.JaasNameCallbackHandler}"/&gt;
 112  
  *     &lt;bean class="{@link JaasPasswordCallbackHandler org.acegisecurity.providers.jaas.JaasPasswordCallbackHandler}"/&gt;
 113  
  *  &lt;/list&gt;
 114  
  * &lt;/property&gt;
 115  
  * </pre>
 116  
  * </p>
 117  
  * <p>
 118  
  * After calling LoginContext.login(), the JaasAuthenticationProvider will retrieve the returned Principals
 119  
  * from the Subject (LoginContext.getSubject().getPrincipals). Each returned principal is then passed to the
 120  
  * configured {@link AuthorityGranter}s. An AuthorityGranter is a mapping between a returned Principal, and a role
 121  
  * name. If an AuthorityGranter wishes to grant an Authorization a role, it returns that role name from it's {@link
 122  
  * AuthorityGranter#grant(java.security.Principal)} method. The returned role will be applied to the Authorization
 123  
  * object as a {@link GrantedAuthority}.</p>
 124  
  * <p>AuthorityGranters are configured in spring xml as follows...
 125  
  * <pre>
 126  
  * &lt;property name="authorityGranters"&gt;
 127  
  *   &lt;list&gt;
 128  
  *     &lt;bean class="org.acegisecurity.providers.jaas.TestAuthorityGranter"/&gt;
 129  
  *   &lt;/list&gt;
 130  
  *  &lt;/property&gt;
 131  
  * </pre>
 132  
  * A configuration note: The JaasAuthenticationProvider uses the security properites
 133  
  * &quote;login.config.url.X&quote; to configure jaas. If you would like to customize the way Jaas gets configured,
 134  
  * create a subclass of this and override the {@link #configureJaas(Resource)} method.
 135  
  * </p>
 136  
  *
 137  
  * @author Ray Krueger
 138  
  * @version $Id: JaasAuthenticationProvider.java 1985 2007-08-29 11:51:02Z luke_t $
 139  
  */
 140  56
 public class JaasAuthenticationProvider implements AuthenticationProvider, ApplicationEventPublisherAware,
 141  
         InitializingBean, ApplicationListener {
 142  
     //~ Static fields/initializers =====================================================================================
 143  
 
 144  3
     protected static final Log log = LogFactory.getLog(JaasAuthenticationProvider.class);
 145  
 
 146  
     //~ Instance fields ================================================================================================
 147  
 
 148  14
     private LoginExceptionResolver loginExceptionResolver = new DefaultLoginExceptionResolver();
 149  
     private Resource loginConfig;
 150  14
     private String loginContextName = "ACEGI";
 151  
     private AuthorityGranter[] authorityGranters;
 152  
     private JaasAuthenticationCallbackHandler[] callbackHandlers;
 153  
     private ApplicationEventPublisher applicationEventPublisher;
 154  
 
 155  
     //~ Methods ========================================================================================================
 156  
 
 157  
         public void afterPropertiesSet() throws Exception {
 158  15
         Assert.notNull(loginConfig, "loginConfig must be set on " + getClass());
 159  14
         Assert.hasLength(loginContextName, "loginContextName must be set on " + getClass());
 160  
 
 161  12
         configureJaas(loginConfig);
 162  
 
 163  12
         Assert.notNull(Configuration.getConfiguration(),
 164  
               "As per http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/Configuration.html "
 165  
             + "\"If a Configuration object was set via the Configuration.setConfiguration method, then that object is "
 166  
             + "returned. Otherwise, a default Configuration object is returned\". Your JRE returned null to "
 167  
             + "Configuration.getConfiguration().");
 168  12
     }
 169  
 
 170  
     /**
 171  
      * Attempts to login the user given the Authentication objects principal and credential
 172  
      *
 173  
      * @param auth The Authentication object to be authenticated.
 174  
      *
 175  
      * @return The authenticated Authentication object, with it's grantedAuthorities set.
 176  
      *
 177  
      * @throws AuthenticationException This implementation does not handle 'locked' or 'disabled' accounts. This method
 178  
      *         only throws a AuthenticationServiceException, with the message of the LoginException that will be
 179  
      *         thrown, should the loginContext.login() method fail.
 180  
      */
 181  
     public Authentication authenticate(Authentication auth)
 182  
         throws AuthenticationException {
 183  7
         if (auth instanceof UsernamePasswordAuthenticationToken) {
 184  6
             UsernamePasswordAuthenticationToken request = (UsernamePasswordAuthenticationToken) auth;
 185  
 
 186  
             try {
 187  
                 //Create the LoginContext object, and pass our InternallCallbackHandler
 188  6
                 LoginContext loginContext = new LoginContext(loginContextName, new InternalCallbackHandler(auth));
 189  
 
 190  
                 //Attempt to login the user, the LoginContext will call our InternalCallbackHandler at this point.
 191  6
                 loginContext.login();
 192  
 
 193  
                 //create a set to hold the authorities, and add any that have already been applied.
 194  4
                 Set authorities = new HashSet();
 195  
 
 196  4
                 if (request.getAuthorities() != null) {
 197  2
                     authorities.addAll(Arrays.asList(request.getAuthorities()));
 198  
                 }
 199  
 
 200  
                 //get the subject principals and pass them to each of the AuthorityGranters
 201  4
                 Set principals = loginContext.getSubject().getPrincipals();
 202  
 
 203  4
                 for (Iterator iterator = principals.iterator(); iterator.hasNext();) {
 204  8
                     Principal principal = (Principal) iterator.next();
 205  
 
 206  16
                     for (int i = 0; i < authorityGranters.length; i++) {
 207  8
                         AuthorityGranter granter = authorityGranters[i];
 208  8
                         Set roles = granter.grant(principal);
 209  
 
 210  
                         //If the granter doesn't wish to grant any authorities, it should return null.
 211  8
                         if ((roles != null) && !roles.isEmpty()) {
 212  4
                             for (Iterator roleIterator = roles.iterator(); roleIterator.hasNext();) {
 213  8
                                 String role = roleIterator.next().toString();
 214  8
                                 authorities.add(new JaasGrantedAuthority(role, principal));
 215  8
                             }
 216  
                         }
 217  
                     }
 218  8
                 }
 219  
 
 220  
                 //Convert the authorities set back to an array and apply it to the token.
 221  4
                 JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
 222  
                         request.getCredentials(),
 223  
                         (GrantedAuthority[]) authorities.toArray(new GrantedAuthority[authorities.size()]), loginContext);
 224  
 
 225  
                 //Publish the success event
 226  4
                 publishSuccessEvent(result);
 227  
 
 228  
                 //we're done, return the token.
 229  4
                 return result;
 230  2
             } catch (LoginException loginException) {
 231  2
                 AcegiSecurityException ase = loginExceptionResolver.resolveException(loginException);
 232  
 
 233  2
                 publishFailureEvent(request, ase);
 234  2
                 throw ase;
 235  
             }
 236  
         }
 237  
 
 238  1
         return null;
 239  
     }
 240  
 
 241  
     /**
 242  
      * Hook method for configuring Jaas
 243  
      *
 244  
      * @param loginConfig URL to Jaas login configuration
 245  
      *
 246  
      * @throws IOException if there is a problem reading the config resource.
 247  
      */
 248  
     protected void configureJaas(Resource loginConfig) throws IOException {
 249  12
         configureJaasUsingLoop();
 250  12
     }
 251  
 
 252  
     /**
 253  
      * Loops through the login.config.url.1,login.config.url.2 properties looking for the login configuration.
 254  
      * If it is not set, it will be set to the last available login.config.url.X property.
 255  
      *
 256  
      */
 257  
     private void configureJaasUsingLoop() throws IOException {
 258  12
         String loginConfigUrl = loginConfig.getURL().toString();
 259  12
         boolean alreadySet = false;
 260  
 
 261  12
         int n = 1;
 262  12
         String prefix = "login.config.url.";
 263  12
         String existing = null;
 264  
 
 265  21
         while ((existing = Security.getProperty(prefix + n)) != null) {
 266  19
             alreadySet = existing.equals(loginConfigUrl);
 267  
 
 268  19
             if (alreadySet) {
 269  10
                 break;
 270  
             }
 271  
 
 272  9
             n++;
 273  
         }
 274  
 
 275  12
         if (!alreadySet) {
 276  2
             String key = prefix + n;
 277  2
             log.debug("Setting security property [" + key + "] to: " + loginConfigUrl);
 278  2
             Security.setProperty(key, loginConfigUrl);
 279  
         }
 280  12
     }
 281  
 
 282  
     /**
 283  
      * Returns the AuthorityGrannter array that was passed to the {@link
 284  
      * #setAuthorityGranters(AuthorityGranter[])} method, or null if it none were ever set.
 285  
      *
 286  
      * @return The AuthorityGranter array, or null
 287  
      *
 288  
      * @see #setAuthorityGranters(AuthorityGranter[])
 289  
      */
 290  
     public AuthorityGranter[] getAuthorityGranters() {
 291  4
         return authorityGranters;
 292  
     }
 293  
 
 294  
     /**
 295  
      * Returns the current JaasAuthenticationCallbackHandler array, or null if none are set.
 296  
      *
 297  
      * @return the JAASAuthenticationCallbackHandlers.
 298  
      *
 299  
      * @see #setCallbackHandlers(JaasAuthenticationCallbackHandler[])
 300  
      */
 301  
     public JaasAuthenticationCallbackHandler[] getCallbackHandlers() {
 302  4
         return callbackHandlers;
 303  
     }
 304  
 
 305  
     public Resource getLoginConfig() {
 306  3
         return loginConfig;
 307  
     }
 308  
 
 309  
     public String getLoginContextName() {
 310  4
         return loginContextName;
 311  
     }
 312  
 
 313  
     public LoginExceptionResolver getLoginExceptionResolver() {
 314  1
         return loginExceptionResolver;
 315  
     }
 316  
 
 317  
     /**
 318  
      * Handles the logout by getting the SecurityContext for the session that was destroyed. <b>MUST NOT use
 319  
      * SecurityContextHolder we are logging out a session that is not related to the current user.</b>
 320  
      *
 321  
      * @param event
 322  
      */
 323  
     protected void handleLogout(HttpSessionDestroyedEvent event) {
 324  1
         SecurityContext context = (SecurityContext)
 325  
                 event.getSession().getAttribute(HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
 326  
 
 327  1
         if (context == null) {
 328  0
             log.debug("The destroyed session has no SecurityContext");
 329  
 
 330  0
             return;
 331  
         }
 332  
 
 333  1
         Authentication auth = context.getAuthentication();
 334  
 
 335  1
         if ((auth != null) && (auth instanceof JaasAuthenticationToken)) {
 336  1
             JaasAuthenticationToken token = (JaasAuthenticationToken) auth;
 337  
 
 338  
             try {
 339  1
                 LoginContext loginContext = token.getLoginContext();
 340  
 
 341  1
                 if (loginContext != null) {
 342  1
                     log.debug("Logging principal: [" + token.getPrincipal() + "] out of LoginContext");
 343  1
                     loginContext.logout();
 344  
                 } else {
 345  0
                     log.debug("Cannot logout principal: [" + token.getPrincipal() + "] from LoginContext. "
 346  
                         + "The LoginContext is unavailable");
 347  
                 }
 348  0
             } catch (LoginException e) {
 349  0
                 log.warn("Error error logging out of LoginContext", e);
 350  1
             }
 351  
         }
 352  1
     }
 353  
 
 354  
     public void onApplicationEvent(ApplicationEvent applicationEvent) {
 355  19
         if (applicationEvent instanceof HttpSessionDestroyedEvent) {
 356  1
             HttpSessionDestroyedEvent event = (HttpSessionDestroyedEvent) applicationEvent;
 357  1
             handleLogout(event);
 358  
         }
 359  19
     }
 360  
 
 361  
     /**
 362  
      * Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by subclasses for different
 363  
      * functionality
 364  
      *
 365  
      * @param token The {@link UsernamePasswordAuthenticationToken} being processed
 366  
      * @param ase The {@link AcegiSecurityException} that caused the failure
 367  
      */
 368  
     protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AcegiSecurityException ase) {
 369  2
         applicationEventPublisher.publishEvent(new JaasAuthenticationFailedEvent(token, ase));
 370  2
     }
 371  
 
 372  
     /**
 373  
      * Publishes the {@link JaasAuthenticationSuccessEvent}. Can be overridden by subclasses for different
 374  
      * functionality.
 375  
      *
 376  
      * @param token The {@link UsernamePasswordAuthenticationToken} being processed
 377  
      */
 378  
     protected void publishSuccessEvent(UsernamePasswordAuthenticationToken token) {
 379  4
         applicationEventPublisher.publishEvent(new JaasAuthenticationSuccessEvent(token));
 380  4
     }
 381  
 
 382  
     /**
 383  
      * Set the AuthorityGranters that should be consulted for role names to be granted to the Authentication.
 384  
      *
 385  
      * @param authorityGranters AuthorityGranter array
 386  
      *
 387  
      * @see JaasAuthenticationProvider
 388  
      */
 389  
     public void setAuthorityGranters(AuthorityGranter[] authorityGranters) {
 390  14
         this.authorityGranters = authorityGranters;
 391  14
     }
 392  
 
 393  
     /**
 394  
      * Set the JAASAuthentcationCallbackHandler array to handle callback objects generated by the
 395  
      * LoginContext.login method.
 396  
      *
 397  
      * @param callbackHandlers Array of JAASAuthenticationCallbackHandlers
 398  
      */
 399  
     public void setCallbackHandlers(JaasAuthenticationCallbackHandler[] callbackHandlers) {
 400  14
         this.callbackHandlers = callbackHandlers;
 401  14
     }
 402  
 
 403  
     /**
 404  
      * Set the JAAS login configuration file.
 405  
      *
 406  
      * @param loginConfig <a
 407  
      *        href="http://www.springframework.org/docs/api/org/springframework/core/io/Resource.html">Spring
 408  
      *        Resource</a>
 409  
      *
 410  
      * @see <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html">JAAS Reference</a>
 411  
      */
 412  
     public void setLoginConfig(Resource loginConfig) {
 413  13
         this.loginConfig = loginConfig;
 414  13
     }
 415  
 
 416  
     /**
 417  
      * Set the loginContextName, this name is used as the index to the configuration specified in the
 418  
      * loginConfig property.
 419  
      *
 420  
      * @param loginContextName
 421  
      */
 422  
     public void setLoginContextName(String loginContextName) {
 423  15
         this.loginContextName = loginContextName;
 424  15
     }
 425  
 
 426  
     public void setLoginExceptionResolver(LoginExceptionResolver loginExceptionResolver) {
 427  1
         this.loginExceptionResolver = loginExceptionResolver;
 428  1
     }
 429  
 
 430  
     public boolean supports(Class aClass) {
 431  3
         return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
 432  
     }
 433  
 
 434  
     public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
 435  14
         this.applicationEventPublisher = applicationEventPublisher;
 436  14
     }
 437  
 
 438  
     protected ApplicationEventPublisher getApplicationEventPublisher() {
 439  1
         return applicationEventPublisher;
 440  
     }
 441  
 
 442  
     //~ Inner Classes ==================================================================================================
 443  
 
 444  
     /**
 445  
      * Wrapper class for JAASAuthenticationCallbackHandlers
 446  
      */
 447  14
     private class InternalCallbackHandler implements CallbackHandler {
 448  
         private Authentication authentication;
 449  
 
 450  6
         public InternalCallbackHandler(Authentication authentication) {
 451  6
             this.authentication = authentication;
 452  6
         }
 453  
 
 454  
         public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
 455  24
             for (int i = 0; i < callbackHandlers.length; i++) {
 456  18
                 JaasAuthenticationCallbackHandler handler = callbackHandlers[i];
 457  
 
 458  72
                 for (int j = 0; j < callbacks.length; j++) {
 459  54
                     Callback callback = callbacks[j];
 460  
 
 461  54
                     handler.handle(callback, authentication);
 462  
                 }
 463  
             }
 464  6
         }
 465  
     }
 466  
 }