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
.equinox
;
18 import java
.util
.Locale
;
20 import java
.util
.UUID
;
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
;
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
;
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";
47 private final static Log log
= LogFactory
.getLog(SpringLoginModule
.class);
49 private AuthenticationManager authenticationManager
;
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 super.initialize(subject
, callbackHandler
, sharedState
, options
);
73 this.callbackHandler
= callbackHandler
;
74 this.subject
= subject
;
77 public boolean login() throws LoginException
{
79 // thread already logged in
80 if (SecurityContextHolder
.getContext().getAuthentication() != null)
83 if (remote
&& anonymous
)
84 throw new LoginException(
85 "Cannot have a Spring login module which is remote and anonymous");
87 // reset all principals and credentials
88 if (log
.isTraceEnabled())
89 log
.trace("Resetting all principals and credentials of "
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();
98 Locale selectedLocale
= null;
99 // deals first with public access since it's simple
102 if (callbackHandler
!= null && availableLocales
!= null
103 && !availableLocales
.trim().equals("")) {
104 LocaleCallback localeCallback
= new LocaleCallback(
106 callbackHandler
.handle(new Callback
[] { localeCallback
});
107 selectedLocale
= localeCallback
.getSelectedLocale();
110 // TODO integrate with JCR?
111 Object principal
= UUID
.randomUUID().toString();
112 GrantedAuthority
[] authorities
= { new GrantedAuthorityImpl(
114 AnonymousAuthenticationToken anonymousToken
= new AnonymousAuthenticationToken(
115 key
, principal
, authorities
);
116 Authentication auth
= authenticationManager
117 .authenticate(anonymousToken
);
118 registerAuthentication(auth
);
120 if (callbackHandler
== null)
121 throw new LoginException("No call back handler available");
123 // ask for username and password
124 NameCallback nameCallback
= new NameCallback("User");
125 PasswordCallback passwordCallback
= new PasswordCallback(
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",
132 LocaleCallback localeCallback
= new LocaleCallback(
137 callbackHandler
.handle(new Callback
[] { nameCallback
,
138 passwordCallback
, urlCallback
, localeCallback
});
140 callbackHandler
.handle(new Callback
[] { nameCallback
,
141 passwordCallback
, localeCallback
});
143 selectedLocale
= localeCallback
.getSelectedLocale();
145 // create credentials
146 String username
= nameCallback
.getName();
147 if (username
== null || username
.trim().equals(""))
150 String password
= "";
151 if (passwordCallback
.getPassword() != null)
152 password
= String
.valueOf(passwordCallback
.getPassword());
154 NodeAuthenticationToken credentials
;
156 String url
= urlCallback
.getName();
157 credentials
= new NodeAuthenticationToken(username
,
160 credentials
= new NodeAuthenticationToken(username
,
164 Authentication authentication
;
166 authentication
= authenticationManager
167 .authenticate(credentials
);
168 } catch (BadCredentialsException e
) {
169 // wait between failed login attempts
170 Thread
.sleep(waitBetweenFailedLoginAttempts
);
173 registerAuthentication(authentication
);
176 if (selectedLocale
!= null)
177 LocaleUtils
.threadLocale
.set(selectedLocale
);
179 return super.login();
180 } catch (LoginException e
) {
182 } catch (ThreadDeath e
) {
183 LoginException le
= new LoginException(
184 "Spring Security login thread died");
187 } catch (Exception e
) {
188 LoginException le
= new LoginException(
189 "Spring Security login failed");
196 public boolean logout() throws LoginException
{
197 subject
.getPrincipals().clear();
198 return super.logout();
202 * Register an {@link Authentication} in the security context.
204 * @param authentication
205 * has to implement {@link Authentication}.
207 protected void registerAuthentication(Object authentication
) {
208 SecurityContextHolder
.getContext().setAuthentication(
209 (Authentication
) authentication
);
212 public void setAuthenticationManager(
213 AuthenticationManager authenticationManager
) {
214 this.authenticationManager
= authenticationManager
;
217 /** Authenticates on a remote node */
218 public void setRemote(Boolean remote
) {
219 this.remote
= remote
;
223 * Request anonymous authentication (incompatible with remote)
225 public void setAnonymous(Boolean anonymous
) {
226 this.anonymous
= anonymous
;
229 /** Role identifying an anonymous user */
230 public void setAnonymousRole(String anonymousRole
) {
231 this.anonymousRole
= anonymousRole
;
235 public void setKey(String key
) {
239 public void setAvailableLocales(String locales
) {
240 this.availableLocales
= locales
;