+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package org.argeo.security.equinox;
import java.util.Map;
+import java.util.UUID;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.login.LoginException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.security.NodeAuthenticationToken;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.BadCredentialsException;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContextHolder;
-import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
+import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
import org.springframework.security.providers.jaas.SecurityContextLoginModule;
/** Login module which caches one subject per thread. */
public class SpringLoginModule extends SecurityContextLoginModule {
+ final static String NODE_REPO_URI = "argeo.node.repo.uri";
+
+ private final static Log log = LogFactory.getLog(SpringLoginModule.class);
+
private AuthenticationManager authenticationManager;
private CallbackHandler callbackHandler;
+ private Subject subject;
+
+ private Long waitBetweenFailedLoginAttempts = 5 * 1000l;
+
+ private Boolean remote = false;
+ private Boolean anonymous = false;
+
+ private String key = null;
+ private String anonymousRole = "ROLE_ANONYMOUS";
+
public SpringLoginModule() {
}
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
super.initialize(subject, callbackHandler, sharedState, options);
- // this.subject.set(subject);
this.callbackHandler = callbackHandler;
+ this.subject = subject;
}
public boolean login() throws LoginException {
- // thread already logged in
- if (SecurityContextHolder.getContext().getAuthentication() != null)
- return super.login();
-
- // if (getSubject().getPrincipals(Authentication.class).size() == 1) {
- // registerAuthentication(getSubject()
- // .getPrincipals(Authentication.class).iterator().next());
- // return super.login();
- // } else if (getSubject().getPrincipals(Authentication.class).size() >
- // 1) {
- // throw new LoginException(
- // "Multiple Authentication principals not supported: "
- // + getSubject().getPrincipals(Authentication.class));
- // } else {
- // ask for username and password
- Callback label = new TextOutputCallback(TextOutputCallback.INFORMATION,
- "Required login");
- NameCallback nameCallback = new NameCallback("User");
- PasswordCallback passwordCallback = new PasswordCallback("Password",
- false);
-
- if (callbackHandler == null) {
- throw new LoginException("No call back handler available");
- // return false;
- }
- try {
- callbackHandler.handle(new Callback[] { label, nameCallback,
- passwordCallback });
- } catch (Exception e) {
- LoginException le = new LoginException("Callback handling failed");
- le.initCause(e);
- throw le;
- }
-
- // Set user name and password
- String username = nameCallback.getName();
- String password = "";
- if (passwordCallback.getPassword() != null) {
- password = String.valueOf(passwordCallback.getPassword());
- }
- UsernamePasswordAuthenticationToken credentials = new UsernamePasswordAuthenticationToken(
- username, password);
-
try {
- Authentication authentication = authenticationManager
- .authenticate(credentials);
+ // thread already logged in
+ if (SecurityContextHolder.getContext().getAuthentication() != null)
+ return super.login();
+
+ if (remote && anonymous)
+ throw new LoginException(
+ "Cannot have a Spring login module which is remote and anonymous");
+
+ // reset all principals and credentials
+ if (log.isTraceEnabled())
+ log.trace("Resetting all principals and credentials of "
+ + subject);
+ if (subject.getPrincipals() != null)
+ subject.getPrincipals().clear();
+ if (subject.getPrivateCredentials() != null)
+ subject.getPrivateCredentials().clear();
+ if (subject.getPublicCredentials() != null)
+ subject.getPublicCredentials().clear();
+
+ // deals first with public access since it's simple
+ if (anonymous) {
+ // TODO integrate with JCR?
+ Object principal = UUID.randomUUID().toString();
+ GrantedAuthority[] authorities = { new GrantedAuthorityImpl(
+ anonymousRole) };
+ AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
+ key, principal, authorities);
+ Authentication auth = authenticationManager
+ .authenticate(anonymousToken);
+ registerAuthentication(auth);
+ return super.login();
+ }
+
+ if (callbackHandler == null)
+ throw new LoginException("No call back handler available");
+
+ // ask for username and password
+ NameCallback nameCallback = new NameCallback("User");
+ PasswordCallback passwordCallback = new PasswordCallback(
+ "Password", false);
+ final String defaultNodeUrl = "http://localhost:7070/org.argeo.jcr.webapp/remoting/node";
+ final String defaultSecurityWorkspace = "security";
+ NameCallback urlCallback = new NameCallback("Site URL",
+ defaultNodeUrl);
+ NameCallback securityWorkspaceCallback = new NameCallback(
+ "Security Workspace", defaultSecurityWorkspace);
+
+ // handle callbacks
+ if (remote)
+ callbackHandler.handle(new Callback[] { nameCallback,
+ passwordCallback, urlCallback,
+ securityWorkspaceCallback });
+ else
+ callbackHandler.handle(new Callback[] { nameCallback,
+ passwordCallback });
+
+ // create credentials
+ String username = nameCallback.getName();
+ if (username == null || username.trim().equals(""))
+ return false;
+
+ String password = "";
+ if (passwordCallback.getPassword() != null)
+ password = String.valueOf(passwordCallback.getPassword());
+
+ NodeAuthenticationToken credentials;
+ if (remote) {
+ String url = urlCallback.getName();
+ String workspace = securityWorkspaceCallback.getName();
+ credentials = new NodeAuthenticationToken(username, password,
+ url, workspace);
+ } else {
+ credentials = new NodeAuthenticationToken(username, password);
+ }
+
+ Authentication authentication;
+ try {
+ authentication = authenticationManager
+ .authenticate(credentials);
+ } catch (BadCredentialsException e) {
+ // wait between failed login attempts
+ Thread.sleep(waitBetweenFailedLoginAttempts);
+ throw e;
+ }
registerAuthentication(authentication);
boolean res = super.login();
- // if (log.isDebugEnabled())
- // log.debug("User " + username + " logged in");
return res;
- } catch (BadCredentialsException bce) {
- throw bce;
+ } catch (LoginException e) {
+ throw e;
+ } catch (ThreadDeath e) {
+ LoginException le = new LoginException(
+ "Spring Security login thread died");
+ le.initCause(e);
+ throw le;
} catch (Exception e) {
- LoginException loginException = new LoginException(
- "Bad credentials");
- loginException.initCause(e);
- throw loginException;
+ LoginException le = new LoginException(
+ "Spring Security login failed");
+ le.initCause(e);
+ throw le;
}
- // }
}
@Override
public boolean logout() throws LoginException {
+ subject.getPrincipals().clear();
return super.logout();
}
this.authenticationManager = authenticationManager;
}
- // protected Subject getSubject() {
- // return subject.get();
- // }
+ /** Authenticates on a remote node */
+ public void setRemote(Boolean remote) {
+ this.remote = remote;
+ }
+
+ /**
+ * Request anonymous authentication (incompatible with remote)
+ */
+ public void setAnonymous(Boolean anonymous) {
+ this.anonymous = anonymous;
+ }
+
+ /** Role identifying an anonymous user */
+ public void setAnonymousRole(String anonymousRole) {
+ this.anonymousRole = anonymousRole;
+ }
+
+ /** System key */
+ public void setKey(String key) {
+ this.key = key;
+ }
}