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.adapters.jboss;
    17
    18  import org.acegisecurity.AccountExpiredException;
    19  import org.acegisecurity.Authentication;
    20  import org.acegisecurity.AuthenticationException;
    21  import org.acegisecurity.AuthenticationManager;
    22  import org.acegisecurity.CredentialsExpiredException;
    23
    24  import org.acegisecurity.adapters.PrincipalAcegiUserToken;
    25
    26  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
    27
    28  import org.jboss.security.SimpleGroup;
    29  import org.jboss.security.SimplePrincipal;
    30  import org.jboss.security.auth.spi.AbstractServerLoginModule;
    31
    32  import org.springframework.beans.factory.access.*;
    33  import org.springframework.beans.factory.access.SingletonBeanFactoryLocator;
    34
    35  import org.springframework.context.support.ClassPathXmlApplicationContext;
    36
    37  import java.security.Principal;
    38  import java.security.acl.Group;
    39
    40  import java.util.Map;
    41
    42  import javax.security.auth.Subject;
    43  import javax.security.auth.callback.Callback;
    44  import javax.security.auth.callback.CallbackHandler;
    45  import javax.security.auth.callback.NameCallback;
    46  import javax.security.auth.callback.PasswordCallback;
    47  import javax.security.auth.callback.UnsupportedCallbackException;
    48  import javax.security.auth.login.FailedLoginException;
    49  import javax.security.auth.login.LoginException;
    50
    51
    52  /***
    53   * Adapter to enable JBoss to authenticate via the Acegi Security System for
    54   * Spring.
    55   *
    56   * <p>
    57   * Returns a {@link PrincipalAcegiUserToken} to JBoss' authentication system,
    58   * which is subsequently available from
    59   * <code>java:comp/env/security/subject</code>.
    60   * </p>
    61   *
    62   * @author Ben Alex
    63   * @author Sergio Bern�
    64   * @version $Id: JbossAcegiLoginModule.java,v 1.11 2005/11/25 00:26:30 benalex Exp $
    65   */
    66  public class JbossAcegiLoginModule extends AbstractServerLoginModule {
    67      //~ Instance fields ========================================================
    68
    69      private AuthenticationManager authenticationManager;
    70      private Principal identity;
    71      private String key;
    72      private char[] credential;
    73
    74      //~ Methods ================================================================
    75
    76      public void initialize(Subject subject, CallbackHandler callbackHandler,
    77          Map sharedState, Map options) {
    78          super.initialize(subject, callbackHandler, sharedState, options);
    79
    80          if (super.log.isInfoEnabled()) {
    81              super.log.info("initializing jboss login module");
    82          }
    83
    84          this.key = (String) options.get("key");
    85
    86          if ((key == null) || "".equals(key)) {
    87              throw new IllegalArgumentException("key must be defined");
    88          }
    89
    90          String singletonId = (String) options.get("singletonId");
    91
    92          String appContextLocation = (String) options.get("appContextLocation");
    93
    94          if ((((singletonId == null) || "".equals(singletonId))
    95              && (appContextLocation == null)) || "".equals(appContextLocation)) {
    96              throw new IllegalArgumentException(
    97                  "appContextLocation must be defined");
    98          }
    99
    100         String beanName = (String) options.get("authenticationManager");
    101
    102         // Attempt to find the appContextLocation only if no singletonId was defined
    103         if ((singletonId == null) || "".equals(singletonId)) {
    104             if (Thread.currentThread().getContextClassLoader().getResource(appContextLocation) == null) {
    105                 if (super.log.isInfoEnabled()) {
    106                     super.log.info("cannot locate " + appContextLocation);
    107                 }
    108
    109                 throw new IllegalArgumentException("Cannot locate "
    110                     + appContextLocation);
    111             }
    112         }
    113
    114         ClassPathXmlApplicationContext ctx = null;
    115
    116         if ((singletonId == null) || "".equals(singletonId)) {
    117             try {
    118                 ctx = new ClassPathXmlApplicationContext(appContextLocation);
    119             } catch (Exception e) {
    120                 if (super.log.isInfoEnabled()) {
    121                     super.log.info("error loading spring context "
    122                         + appContextLocation + " " + e);
    123                 }
    124
    125                 throw new IllegalArgumentException(
    126                     "error loading spring context " + appContextLocation + " "
    127                     + e);
    128             }
    129         } else {
    130             if (super.log.isInfoEnabled()) {
    131                 super.log.debug("retrieving singleton instance " + singletonId);
    132             }
    133
    134             BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
    135             BeanFactoryReference bf = bfl.useBeanFactory(singletonId);
    136             ctx = (ClassPathXmlApplicationContext) bf.getFactory();
    137
    138             if (ctx == null) {
    139                 if (super.log.isInfoEnabled()) {
    140                     super.log.info("singleton " + beanName + " does not exists");
    141                 }
    142
    143                 throw new IllegalArgumentException("singleton " + singletonId
    144                     + " does not exists");
    145             }
    146         }
    147
    148         if ((beanName == null) || "".equals(beanName)) {
    149             Map beans = null;
    150
    151             try {
    152                 beans = ctx.getBeansOfType(AuthenticationManager.class, true,
    153                         true);
    154             } catch (Exception e) {
    155                 if (super.log.isInfoEnabled()) {
    156                     super.log.info("exception in getBeansOfType " + e);
    157                 }
    158
    159                 throw new IllegalStateException(
    160                     "spring error in get beans by class");
    161             }
    162
    163             if (beans.size() == 0) {
    164                 throw new IllegalArgumentException(
    165                     "Bean context must contain at least one bean of type AuthenticationManager");
    166             }
    167
    168             beanName = (String) beans.keySet().iterator().next();
    169         }
    170
    171         authenticationManager = (AuthenticationManager) ctx.getBean(beanName);
    172
    173         if (super.log.isInfoEnabled()) {
    174             super.log.info("Successfully started JbossSpringLoginModule");
    175         }
    176     }
    177
    178     public boolean login() throws LoginException {
    179         super.loginOk = false;
    180
    181         String[] info = getUsernameAndPassword();
    182         String username = info[0];
    183         String password = info[1];
    184
    185         if ((username == null) && (password == null)) {
    186             identity = null;
    187             super.log.trace("Authenticating as unauthenticatedIdentity="
    188                 + identity);
    189         }
    190
    191         if (username == null) {
    192             username = "";
    193         }
    194
    195         if (password == null) {
    196             password = "";
    197         }
    198
    199         if (super.log.isDebugEnabled()) {
    200             super.log.debug("checking identity");
    201         }
    202
    203         if (identity == null) {
    204             super.log.debug("creating usernamepassword token");
    205
    206             Authentication request = new UsernamePasswordAuthenticationToken(username,
    207                     password);
    208             Authentication response = null;
    209
    210             try {
    211                 if (super.log.isDebugEnabled()) {
    212                     super.log.debug("attempting authentication");
    213                 }
    214
    215                 response = authenticationManager.authenticate(request);
    216
    217                 if (super.log.isDebugEnabled()) {
    218                     super.log.debug("authentication succeded");
    219                 }
    220             } catch (CredentialsExpiredException cee) {
    221                 if (super.log.isDebugEnabled()) {
    222                     super.log.debug("Credential has expired");
    223                 }
    224
    225                 throw new javax.security.auth.login.CredentialExpiredException(
    226                     "The credential used to identify the user has expired");
    227             } catch (AccountExpiredException cee) {
    228                 if (super.log.isDebugEnabled()) {
    229                     super.log.debug(
    230                         "Account has expired, throwing jaas exception");
    231                 }
    232
    233                 throw new javax.security.auth.login.AccountExpiredException(
    234                     "The account specified in login has expired");
    235             } catch (AuthenticationException failed) {
    236                 if (super.log.isDebugEnabled()) {
    237                     super.log.debug("Bad password for username=" + username);
    238                 }
    239
    240                 throw new FailedLoginException(
    241                     "Password Incorrect/Password Required");
    242             }
    243
    244             super.log.debug("user is logged. redirecting to jaas classes");
    245
    246             identity = new PrincipalAcegiUserToken(this.key,
    247                     response.getName(), response.getCredentials().toString(),
    248                     response.getAuthorities(), response.getPrincipal());
    249         }
    250
    251         if (getUseFirstPass() == true) {
    252             // Add the username and password to the shared state map
    253             sharedState.put("javax.security.auth.login.name", username);
    254             sharedState.put("javax.security.auth.login.password", credential);
    255         }
    256
    257         super.loginOk = true;
    258         super.log.trace("User '" + identity + "' authenticated, loginOk="
    259             + loginOk);
    260
    261         return true;
    262     }
    263
    264     protected Principal getIdentity() {
    265         return this.identity;
    266     }
    267
    268     protected Group[] getRoleSets() throws LoginException {
    269         SimpleGroup roles = new SimpleGroup("Roles");
    270         Group[] roleSets = {roles};
    271
    272         if (this.identity instanceof Authentication) {
    273             Authentication user = (Authentication) this.identity;
    274
    275             for (int i = 0; i < user.getAuthorities().length; i++) {
    276                 roles.addMember(new SimplePrincipal(
    277                         user.getAuthorities()[i].getAuthority()));
    278             }
    279         }
    280
    281         return roleSets;
    282     }
    283
    284     protected String[] getUsernameAndPassword() throws LoginException {
    285         String[] info = {null, null};
    286
    287         // prompt for a username and password
    288         if (callbackHandler == null) {
    289             throw new LoginException("Error: no CallbackHandler available "
    290                 + "to collect authentication information");
    291         }
    292
    293         NameCallback nc = new NameCallback("User name: ", "guest");
    294         PasswordCallback pc = new PasswordCallback("Password: ", false);
    295         Callback[] callbacks = {nc, pc};
    296         String username = null;
    297         String password = null;
    298
    299         try {
    300             callbackHandler.handle(callbacks);
    301             username = nc.getName();
    302
    303             char[] tmpPassword = pc.getPassword();
    304
    305             if (tmpPassword != null) {
    306                 credential = new char[tmpPassword.length];
    307                 System.arraycopy(tmpPassword, 0, credential, 0,
    308                     tmpPassword.length);
    309                 pc.clearPassword();
    310                 password = new String(credential);
    311             }
    312         } catch (java.io.IOException ioe) {
    313             throw new LoginException(ioe.toString());
    314         } catch (UnsupportedCallbackException uce) {
    315             throw new LoginException("CallbackHandler does not support: "
    316                 + uce.getCallback());
    317         }
    318
    319         info[0] = username;
    320         info[1] = password;
    321
    322         return info;
    323     }
    324 }