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 }