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 public 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 if (SecurityContextHolder
.getContext().getAuthentication() != null)
84 if (remote
&& anonymous
)
85 throw new LoginException(
86 "Cannot have a Spring login module which is remote and anonymous");
88 // reset all principals and credentials
89 if (log
.isTraceEnabled())
90 log
.trace("Resetting all principals and credentials of "
92 subject
.getPrincipals().clear();
93 subject
.getPrivateCredentials().clear();
94 subject
.getPublicCredentials().clear();
96 Locale selectedLocale
= null;
97 // deals first with public access since it's simple
100 if (callbackHandler
!= null && availableLocales
!= null
101 && !availableLocales
.trim().equals("")) {
102 LocaleCallback localeCallback
= new LocaleCallback(
104 callbackHandler
.handle(new Callback
[] { localeCallback
});
105 selectedLocale
= localeCallback
.getSelectedLocale();
108 // TODO integrate with JCR?
109 Object principal
= UUID
.randomUUID().toString();
110 List
<SimpleGrantedAuthority
> authorities
= Collections
111 .singletonList(new SimpleGrantedAuthority(anonymousRole
));
112 AnonymousAuthenticationToken anonymousToken
= new AnonymousAuthenticationToken(
113 key
, principal
, authorities
);
114 Authentication auth
= authenticationManager
115 .authenticate(anonymousToken
);
116 registerAuthentication(auth
);
118 if (callbackHandler
== null)
119 throw new LoginException("No call back handler available");
121 // ask for username and password
122 NameCallback nameCallback
= new NameCallback("User");
123 PasswordCallback passwordCallback
= new PasswordCallback(
125 final String defaultNodeUrl
= System
126 .getProperty(NODE_REPO_URI
,
127 "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
128 NameCallback urlCallback
= new NameCallback("Site URL",
130 LocaleCallback localeCallback
= new LocaleCallback(
135 callbackHandler
.handle(new Callback
[] { nameCallback
,
136 passwordCallback
, urlCallback
, localeCallback
});
138 callbackHandler
.handle(new Callback
[] { nameCallback
,
139 passwordCallback
, localeCallback
});
141 selectedLocale
= localeCallback
.getSelectedLocale();
143 // create credentials
144 final String username
= nameCallback
.getName();
145 if (username
== null || username
.trim().equals(""))
148 char[] password
= {};
149 if (passwordCallback
.getPassword() != null)
150 password
= passwordCallback
.getPassword();
152 NodeAuthenticationToken credentials
;
154 String url
= urlCallback
.getName();
155 credentials
= new NodeAuthenticationToken(username
,
158 credentials
= new NodeAuthenticationToken(username
,
162 Authentication authentication
;
164 authentication
= authenticationManager
165 .authenticate(credentials
);
166 } catch (BadCredentialsException e
) {
167 // wait between failed login attempts
168 Thread
.sleep(waitBetweenFailedLoginAttempts
);
171 registerAuthentication(authentication
);
172 subject
.getPrincipals().add(authentication
);
175 if (selectedLocale
!= null)
176 LocaleUtils
.threadLocale
.set(selectedLocale
);
178 return super.login();
179 } catch (LoginException e
) {
181 } catch (ThreadDeath e
) {
182 LoginException le
= new LoginException(
183 "Spring Security login thread died");
186 } catch (Exception e
) {
187 LoginException le
= new LoginException(
188 "Spring Security login failed");
195 public boolean logout() throws LoginException
{
196 subject
.getPrincipals().clear();
197 return super.logout();
201 * Register an {@link Authentication} in the security context.
203 * @param authentication
204 * has to implement {@link Authentication}.
206 protected void registerAuthentication(Object authentication
) {
207 SecurityContextHolder
.getContext().setAuthentication(
208 (Authentication
) authentication
);
211 public void setAuthenticationManager(
212 AuthenticationManager authenticationManager
) {
213 this.authenticationManager
= authenticationManager
;
216 /** Authenticates on a remote node */
217 public void setRemote(Boolean remote
) {
218 this.remote
= remote
;
222 * Request anonymous authentication (incompatible with remote)
224 public void setAnonymous(Boolean anonymous
) {
225 this.anonymous
= anonymous
;
228 /** Role identifying an anonymous user */
229 public void setAnonymousRole(String anonymousRole
) {
230 this.anonymousRole
= anonymousRole
;
234 public void setKey(String key
) {
238 public void setAvailableLocales(String locales
) {
239 this.availableLocales
= locales
;