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