2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.security
.core
;
18 import java
.util
.Collections
;
19 import java
.util
.List
;
20 import java
.util
.Locale
;
22 import java
.util
.UUID
;
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 import javax
.security
.auth
.spi
.LoginModule
;
32 import org
.apache
.commons
.logging
.Log
;
33 import org
.apache
.commons
.logging
.LogFactory
;
34 import org
.argeo
.security
.NodeAuthenticationToken
;
35 import org
.argeo
.util
.LocaleCallback
;
36 import org
.argeo
.util
.LocaleUtils
;
37 import org
.osgi
.framework
.BundleContext
;
38 import org
.springframework
.security
.authentication
.AnonymousAuthenticationToken
;
39 import org
.springframework
.security
.authentication
.AuthenticationManager
;
40 import org
.springframework
.security
.authentication
.BadCredentialsException
;
41 import org
.springframework
.security
.core
.Authentication
;
42 import org
.springframework
.security
.core
.authority
.SimpleGrantedAuthority
;
43 import org
.springframework
.security
.core
.context
.SecurityContextHolder
;
45 /** Login module which caches one subject per thread. */
46 public class SpringLoginModule
implements LoginModule
{
47 final static String NODE_REPO_URI
= "argeo.node.repo.uri";
49 private final static Log log
= LogFactory
.getLog(SpringLoginModule
.class);
51 private CallbackHandler callbackHandler
;
53 private Subject subject
;
55 private Long waitBetweenFailedLoginAttempts
= 5 * 1000l;
57 private Boolean remote
= false;
58 private Boolean anonymous
= false;
59 /** Comma separated list of locales */
60 private String availableLocales
= "";
62 private String key
= null;
63 private String anonymousRole
= "ROLE_ANONYMOUS";
65 public SpringLoginModule() {
69 @SuppressWarnings("rawtypes")
70 public void initialize(Subject subject
, CallbackHandler callbackHandler
,
71 Map sharedState
, Map options
) {
72 this.callbackHandler
= callbackHandler
;
73 this.subject
= subject
;
76 public boolean login() throws LoginException
{
78 // thread already logged in
79 Authentication currentAuth
= SecurityContextHolder
.getContext()
81 if (currentAuth
!= null) {
82 if (subject
.getPrincipals(Authentication
.class).size() == 0) {
83 subject
.getPrincipals().add(currentAuth
);
85 Authentication principal
= subject
86 .getPrincipals(Authentication
.class).iterator()
88 if (principal
!= currentAuth
)
89 throw new LoginException(
90 "Already authenticated with a different auth");
95 if (remote
&& anonymous
)
96 throw new LoginException(
97 "Cannot have a Spring login module which is remote and anonymous");
99 // reset all principals and credentials
100 if (log
.isTraceEnabled())
101 log
.trace("Resetting all principals and credentials of "
103 subject
.getPrincipals().clear();
104 subject
.getPrivateCredentials().clear();
105 subject
.getPublicCredentials().clear();
107 Locale selectedLocale
= null;
108 // deals first with public access since it's simple
110 // FIXME Is this code still needed?
111 AuthenticationManager authenticationManager
= null;
114 if (callbackHandler
!= null && availableLocales
!= null
115 && !availableLocales
.trim().equals("")) {
116 LocaleCallback localeCallback
= new LocaleCallback(
118 callbackHandler
.handle(new Callback
[] { localeCallback
});
119 selectedLocale
= localeCallback
.getSelectedLocale();
122 // TODO integrate with JCR?
123 Object principal
= UUID
.randomUUID().toString();
124 List
<SimpleGrantedAuthority
> authorities
= Collections
125 .singletonList(new SimpleGrantedAuthority(anonymousRole
));
126 AnonymousAuthenticationToken anonymousToken
= new AnonymousAuthenticationToken(
127 key
, principal
, authorities
);
128 Authentication auth
= authenticationManager
129 .authenticate(anonymousToken
);
130 registerAuthentication(auth
);
132 if (callbackHandler
== null)
133 throw new LoginException("No call back handler available");
135 // ask for username and password
136 NameCallback nameCallback
= new NameCallback("User");
137 PasswordCallback passwordCallback
= new PasswordCallback(
139 final String defaultNodeUrl
= System
140 .getProperty(NODE_REPO_URI
,
141 "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
142 NameCallback urlCallback
= new NameCallback("Site URL",
144 LocaleCallback localeCallback
= new LocaleCallback(
146 BundleContextCallback bundleContextCallback
= new BundleContextCallback();
150 callbackHandler
.handle(new Callback
[] { nameCallback
,
151 passwordCallback
, urlCallback
, localeCallback
,
152 bundleContextCallback
});
154 callbackHandler
.handle(new Callback
[] { nameCallback
,
155 passwordCallback
, localeCallback
,
156 bundleContextCallback
});
158 selectedLocale
= localeCallback
.getSelectedLocale();
160 // create credentials
161 final String username
= nameCallback
.getName();
162 if (username
== null || username
.trim().equals(""))
165 char[] password
= {};
166 if (passwordCallback
.getPassword() != null)
167 password
= passwordCallback
.getPassword();
169 NodeAuthenticationToken credentials
;
171 String url
= urlCallback
.getName();
172 credentials
= new NodeAuthenticationToken(username
,
175 credentials
= new NodeAuthenticationToken(username
,
179 BundleContext bc
= bundleContextCallback
.getBundleContext();
180 AuthenticationManager authenticationManager
= bc
.getService(bc
181 .getServiceReference(AuthenticationManager
.class));
183 Authentication authentication
;
185 authentication
= authenticationManager
186 .authenticate(credentials
);
187 } catch (BadCredentialsException e
) {
188 // wait between failed login attempts
189 Thread
.sleep(waitBetweenFailedLoginAttempts
);
192 registerAuthentication(authentication
);
193 subject
.getPrincipals().add(authentication
);
196 if (selectedLocale
!= null)
197 LocaleUtils
.threadLocale
.set(selectedLocale
);
200 } catch (LoginException e
) {
202 } catch (ThreadDeath e
) {
203 LoginException le
= new LoginException(
204 "Spring Security login thread died");
207 } catch (Exception e
) {
208 LoginException le
= new LoginException(
209 "Spring Security login failed");
216 public boolean logout() throws LoginException
{
217 subject
.getPrincipals().clear();
222 public boolean commit() throws LoginException
{
227 public boolean abort() throws LoginException
{
232 * Register an {@link Authentication} in the security context.
234 * @param authentication
235 * has to implement {@link Authentication}.
237 protected void registerAuthentication(Object authentication
) {
238 SecurityContextHolder
.getContext().setAuthentication(
239 (Authentication
) authentication
);
242 /** Authenticates on a remote node */
243 public void setRemote(Boolean remote
) {
244 this.remote
= remote
;
248 * Request anonymous authentication (incompatible with remote)
250 public void setAnonymous(Boolean anonymous
) {
251 this.anonymous
= anonymous
;
254 /** Role identifying an anonymous user */
255 public void setAnonymousRole(String anonymousRole
) {
256 this.anonymousRole
= anonymousRole
;
260 public void setKey(String key
) {
264 public void setAvailableLocales(String locales
) {
265 this.availableLocales
= locales
;