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
.cms
.internal
.kernel
;
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
;
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
;
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";
48 private final static Log log
= LogFactory
.getLog(SpringLoginModule
.class);
50 private AuthenticationManager authenticationManager
;
52 private CallbackHandler callbackHandler
;
54 private Subject subject
;
56 private Long waitBetweenFailedLoginAttempts
= 5 * 1000l;
58 private Boolean remote
= false;
59 private Boolean anonymous
= false;
60 /** Comma separated list of locales */
61 private String availableLocales
= "";
63 private String key
= null;
64 private String anonymousRole
= "ROLE_ANONYMOUS";
66 public SpringLoginModule() {
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
;
78 public boolean login() throws LoginException
{
80 // thread already logged in
81 Authentication currentAuth
= SecurityContextHolder
.getContext()
83 if (currentAuth
!= null) {
84 if (subject
.getPrincipals(Authentication
.class).size() == 0) {
85 subject
.getPrincipals().add(currentAuth
);
87 Authentication principal
= subject
88 .getPrincipals(Authentication
.class).iterator()
90 if (principal
!= currentAuth
)
91 throw new LoginException(
92 "Already authenticated with a different auth");
97 if (remote
&& anonymous
)
98 throw new LoginException(
99 "Cannot have a Spring login module which is remote and anonymous");
101 // reset all principals and credentials
102 if (log
.isTraceEnabled())
103 log
.trace("Resetting all principals and credentials of "
105 subject
.getPrincipals().clear();
106 subject
.getPrivateCredentials().clear();
107 subject
.getPublicCredentials().clear();
109 Locale selectedLocale
= null;
110 // deals first with public access since it's simple
113 if (callbackHandler
!= null && availableLocales
!= null
114 && !availableLocales
.trim().equals("")) {
115 LocaleCallback localeCallback
= new LocaleCallback(
117 callbackHandler
.handle(new Callback
[] { localeCallback
});
118 selectedLocale
= localeCallback
.getSelectedLocale();
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
);
131 if (callbackHandler
== null)
132 throw new LoginException("No call back handler available");
134 // ask for username and password
135 NameCallback nameCallback
= new NameCallback("User");
136 PasswordCallback passwordCallback
= new PasswordCallback(
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",
143 LocaleCallback localeCallback
= new LocaleCallback(
148 callbackHandler
.handle(new Callback
[] { nameCallback
,
149 passwordCallback
, urlCallback
, localeCallback
});
151 callbackHandler
.handle(new Callback
[] { nameCallback
,
152 passwordCallback
, localeCallback
});
154 selectedLocale
= localeCallback
.getSelectedLocale();
156 // create credentials
157 final String username
= nameCallback
.getName();
158 if (username
== null || username
.trim().equals(""))
161 char[] password
= {};
162 if (passwordCallback
.getPassword() != null)
163 password
= passwordCallback
.getPassword();
165 NodeAuthenticationToken credentials
;
167 String url
= urlCallback
.getName();
168 credentials
= new NodeAuthenticationToken(username
,
171 credentials
= new NodeAuthenticationToken(username
,
175 Authentication authentication
;
177 authentication
= authenticationManager
178 .authenticate(credentials
);
179 } catch (BadCredentialsException e
) {
180 // wait between failed login attempts
181 Thread
.sleep(waitBetweenFailedLoginAttempts
);
184 registerAuthentication(authentication
);
185 subject
.getPrincipals().add(authentication
);
188 if (selectedLocale
!= null)
189 LocaleUtils
.threadLocale
.set(selectedLocale
);
191 return super.login();
192 } catch (LoginException e
) {
194 } catch (ThreadDeath e
) {
195 LoginException le
= new LoginException(
196 "Spring Security login thread died");
199 } catch (Exception e
) {
200 LoginException le
= new LoginException(
201 "Spring Security login failed");
208 public boolean logout() throws LoginException
{
209 subject
.getPrincipals().clear();
210 return super.logout();
214 * Register an {@link Authentication} in the security context.
216 * @param authentication
217 * has to implement {@link Authentication}.
219 protected void registerAuthentication(Object authentication
) {
220 SecurityContextHolder
.getContext().setAuthentication(
221 (Authentication
) authentication
);
224 public void setAuthenticationManager(
225 AuthenticationManager authenticationManager
) {
226 this.authenticationManager
= authenticationManager
;
229 /** Authenticates on a remote node */
230 public void setRemote(Boolean remote
) {
231 this.remote
= remote
;
235 * Request anonymous authentication (incompatible with remote)
237 public void setAnonymous(Boolean anonymous
) {
238 this.anonymous
= anonymous
;
241 /** Role identifying an anonymous user */
242 public void setAnonymousRole(String anonymousRole
) {
243 this.anonymousRole
= anonymousRole
;
247 public void setKey(String key
) {
251 public void setAvailableLocales(String locales
) {
252 this.availableLocales
= locales
;