View Javadoc

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