]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/SpringLoginModule.java
Use char array for password
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / SpringLoginModule.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.cms.internal.kernel;
17
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Locale;
21 import java.util.Map;
22 import java.util.UUID;
23
24 import javax.security.auth.Subject;
25 import javax.security.auth.callback.Callback;
26 import javax.security.auth.callback.CallbackHandler;
27 import javax.security.auth.callback.NameCallback;
28 import javax.security.auth.callback.PasswordCallback;
29 import javax.security.auth.login.LoginException;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.argeo.security.NodeAuthenticationToken;
34 import org.argeo.util.LocaleCallback;
35 import org.argeo.util.LocaleUtils;
36 import org.springframework.security.authentication.AnonymousAuthenticationToken;
37 import org.springframework.security.authentication.AuthenticationManager;
38 import org.springframework.security.authentication.BadCredentialsException;
39 import org.springframework.security.authentication.jaas.SecurityContextLoginModule;
40 import org.springframework.security.core.Authentication;
41 import org.springframework.security.core.authority.SimpleGrantedAuthority;
42 import org.springframework.security.core.context.SecurityContextHolder;
43
44 /** Login module which caches one subject per thread. */
45 class SpringLoginModule extends SecurityContextLoginModule {
46 final static String NODE_REPO_URI = "argeo.node.repo.uri";
47
48 private final static Log log = LogFactory.getLog(SpringLoginModule.class);
49
50 private AuthenticationManager authenticationManager;
51
52 private CallbackHandler callbackHandler;
53
54 private Subject subject;
55
56 private Long waitBetweenFailedLoginAttempts = 5 * 1000l;
57
58 private Boolean remote = false;
59 private Boolean anonymous = false;
60 /** Comma separated list of locales */
61 private String availableLocales = "";
62
63 private String key = null;
64 private String anonymousRole = "ROLE_ANONYMOUS";
65
66 public SpringLoginModule() {
67
68 }
69
70 @SuppressWarnings("rawtypes")
71 public void initialize(Subject subject, CallbackHandler callbackHandler,
72 Map sharedState, Map options) {
73 super.initialize(subject, callbackHandler, sharedState, options);
74 this.callbackHandler = callbackHandler;
75 this.subject = subject;
76 }
77
78 public boolean login() throws LoginException {
79 try {
80 // thread already logged in
81 Authentication currentAuth = SecurityContextHolder.getContext()
82 .getAuthentication();
83 if (currentAuth != null) {
84 if (subject.getPrincipals(Authentication.class).size() == 0) {
85 subject.getPrincipals().add(currentAuth);
86 } else {
87 Authentication principal = subject
88 .getPrincipals(Authentication.class).iterator()
89 .next();
90 if (principal != currentAuth)
91 throw new LoginException(
92 "Already authenticated with a different auth");
93 }
94 return super.login();
95 }
96
97 if (remote && anonymous)
98 throw new LoginException(
99 "Cannot have a Spring login module which is remote and anonymous");
100
101 // reset all principals and credentials
102 if (log.isTraceEnabled())
103 log.trace("Resetting all principals and credentials of "
104 + subject);
105 subject.getPrincipals().clear();
106 subject.getPrivateCredentials().clear();
107 subject.getPublicCredentials().clear();
108
109 Locale selectedLocale = null;
110 // deals first with public access since it's simple
111 if (anonymous) {
112 // multi locale
113 if (callbackHandler != null && availableLocales != null
114 && !availableLocales.trim().equals("")) {
115 LocaleCallback localeCallback = new LocaleCallback(
116 availableLocales);
117 callbackHandler.handle(new Callback[] { localeCallback });
118 selectedLocale = localeCallback.getSelectedLocale();
119 }
120
121 // TODO integrate with JCR?
122 Object principal = UUID.randomUUID().toString();
123 List<SimpleGrantedAuthority> authorities = Collections
124 .singletonList(new SimpleGrantedAuthority(anonymousRole));
125 AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
126 key, principal, authorities);
127 Authentication auth = authenticationManager
128 .authenticate(anonymousToken);
129 registerAuthentication(auth);
130 } else {
131 if (callbackHandler == null)
132 throw new LoginException("No call back handler available");
133
134 // ask for username and password
135 NameCallback nameCallback = new NameCallback("User");
136 PasswordCallback passwordCallback = new PasswordCallback(
137 "Password", false);
138 final String defaultNodeUrl = System
139 .getProperty(NODE_REPO_URI,
140 "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
141 NameCallback urlCallback = new NameCallback("Site URL",
142 defaultNodeUrl);
143 LocaleCallback localeCallback = new LocaleCallback(
144 availableLocales);
145
146 // handle callbacks
147 if (remote)
148 callbackHandler.handle(new Callback[] { nameCallback,
149 passwordCallback, urlCallback, localeCallback });
150 else
151 callbackHandler.handle(new Callback[] { nameCallback,
152 passwordCallback, localeCallback });
153
154 selectedLocale = localeCallback.getSelectedLocale();
155
156 // create credentials
157 final String username = nameCallback.getName();
158 if (username == null || username.trim().equals(""))
159 return false;
160
161 char[] password = {};
162 if (passwordCallback.getPassword() != null)
163 password = passwordCallback.getPassword();
164
165 NodeAuthenticationToken credentials;
166 if (remote) {
167 String url = urlCallback.getName();
168 credentials = new NodeAuthenticationToken(username,
169 password, url);
170 } else {
171 credentials = new NodeAuthenticationToken(username,
172 password);
173 }
174
175 Authentication authentication;
176 try {
177 authentication = authenticationManager
178 .authenticate(credentials);
179 } catch (BadCredentialsException e) {
180 // wait between failed login attempts
181 Thread.sleep(waitBetweenFailedLoginAttempts);
182 throw e;
183 }
184 registerAuthentication(authentication);
185 subject.getPrincipals().add(authentication);
186 }
187
188 if (selectedLocale != null)
189 LocaleUtils.threadLocale.set(selectedLocale);
190
191 return super.login();
192 } catch (LoginException e) {
193 throw e;
194 } catch (ThreadDeath e) {
195 LoginException le = new LoginException(
196 "Spring Security login thread died");
197 le.initCause(e);
198 throw le;
199 } catch (Exception e) {
200 LoginException le = new LoginException(
201 "Spring Security login failed");
202 le.initCause(e);
203 throw le;
204 }
205 }
206
207 @Override
208 public boolean logout() throws LoginException {
209 subject.getPrincipals().clear();
210 return super.logout();
211 }
212
213 /**
214 * Register an {@link Authentication} in the security context.
215 *
216 * @param authentication
217 * has to implement {@link Authentication}.
218 */
219 protected void registerAuthentication(Object authentication) {
220 SecurityContextHolder.getContext().setAuthentication(
221 (Authentication) authentication);
222 }
223
224 public void setAuthenticationManager(
225 AuthenticationManager authenticationManager) {
226 this.authenticationManager = authenticationManager;
227 }
228
229 /** Authenticates on a remote node */
230 public void setRemote(Boolean remote) {
231 this.remote = remote;
232 }
233
234 /**
235 * Request anonymous authentication (incompatible with remote)
236 */
237 public void setAnonymous(Boolean anonymous) {
238 this.anonymous = anonymous;
239 }
240
241 /** Role identifying an anonymous user */
242 public void setAnonymousRole(String anonymousRole) {
243 this.anonymousRole = anonymousRole;
244 }
245
246 /** System key */
247 public void setKey(String key) {
248 this.key = key;
249 }
250
251 public void setAvailableLocales(String locales) {
252 this.availableLocales = locales;
253 }
254
255 }