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