From 0aa4989dad9040823cdbb0539a99f0fc95474106 Mon Sep 17 00:00:00 2001
From: Ray Krueger
Date: Fri, 4 Nov 2005 15:02:27 +0000
Subject: [PATCH] JaasAuthenticationProvider no longer supports the
useSystemProperty setting.This is because it no longer uses the
java.security.auth.login.config system property for configuring Jaas. Custom
Jaas configuration needs can be implemented in a subclass that overrides the
configureJaas method.
JaasAuthenticationProvider now handles logout by associating the LoginContext with a new JaasAuthenticationToken
---
.../jaas/JaasAuthenticationProvider.java | 242 +++++++++---------
.../jaas/JaasAuthenticationToken.java | 31 +++
.../jaas/JaasAuthenticationProviderTests.java | 36 ++-
3 files changed, 182 insertions(+), 127 deletions(-)
create mode 100644 core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationToken.java
diff --git a/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationProvider.java
index 51157ac562..d29179dc42 100644
--- a/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationProvider.java
+++ b/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationProvider.java
@@ -19,40 +19,37 @@ import net.sf.acegisecurity.AcegiSecurityException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter;
+import net.sf.acegisecurity.context.SecurityContext;
import net.sf.acegisecurity.providers.AuthenticationProvider;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.jaas.event.JaasAuthenticationFailedEvent;
import net.sf.acegisecurity.providers.jaas.event.JaasAuthenticationSuccessEvent;
-
+import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
-
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
-
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
import org.springframework.core.io.Resource;
-
import org.springframework.util.Assert;
-import java.io.IOException;
-
-import java.security.Principal;
-import java.security.Security;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.security.Principal;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
/**
@@ -157,15 +154,14 @@ import javax.security.auth.login.LoginException;
*
*
* A configuration note:
- * The JaasAuthenticationProvider configures jaas using the system property 'java.security.auth.login.config' by default.
- * If use of the java.security.auth.login.config property is not allowed by the Security property 'policy.allowSystemProperty', OR if the JaasAuthenticationProvider
- * useSystemProperty option is false, then Jaas will be configured using the 'login.config.url.x' properties.
+ * The JaasAuthenticationProvider uses the security properites "e;login.config.url.X"e; to configure jaas.
+ * If you would like to customize the way Jaas gets configured, create a subclass of this and override the {@link #configureJaas(Resource)} method.
*
* @author Ray Krueger
* @version $Id$
*/
public class JaasAuthenticationProvider implements AuthenticationProvider,
- InitializingBean, ApplicationContextAware {
+ InitializingBean, ApplicationContextAware, ApplicationListener {
//~ Static fields/initializers =============================================
protected static final Log log = LogFactory.getLog(JaasAuthenticationProvider.class);
@@ -175,16 +171,15 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
private ApplicationContext context;
private LoginExceptionResolver loginExceptionResolver = new DefaultLoginExceptionResolver();
private Resource loginConfig;
- private final String SYSPROP = "java.security.auth.login.config";
+
private String loginContextName = "ACEGI";
private AuthorityGranter[] authorityGranters;
private JaasAuthenticationCallbackHandler[] callbackHandlers;
- private boolean useSystemProperty = true;
//~ Methods ================================================================
public void setApplicationContext(ApplicationContext applicationContext)
- throws BeansException {
+ throws BeansException {
this.context = applicationContext;
}
@@ -197,7 +192,6 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* granted to the Authentication.
*
* @param authorityGranters AuthorityGranter array
- *
* @see JaasAuthenticationProvider
*/
public void setAuthorityGranters(AuthorityGranter[] authorityGranters) {
@@ -210,8 +204,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* were ever set.
*
* @return The AuthorityGranter array, or null
- *
- * @see #setAuthorityGranters(net.sf.acegisecurity.providers.jaas.AuthorityGranter[])
+ * @see #setAuthorityGranters(AuthorityGranter[])
*/
public AuthorityGranter[] getAuthorityGranters() {
return authorityGranters;
@@ -224,7 +217,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* @param callbackHandlers Array of JAASAuthenticationCallbackHandlers
*/
public void setCallbackHandlers(
- JaasAuthenticationCallbackHandler[] callbackHandlers) {
+ JaasAuthenticationCallbackHandler[] callbackHandlers) {
this.callbackHandlers = callbackHandlers;
}
@@ -233,8 +226,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* none are set.
*
* @return the JAASAuthenticationCallbackHandlers.
- *
- * @see #setCallbackHandlers(net.sf.acegisecurity.providers.jaas.JaasAuthenticationCallbackHandler[])
+ * @see #setCallbackHandlers(JaasAuthenticationCallbackHandler[])
*/
public JaasAuthenticationCallbackHandler[] getCallbackHandlers() {
return callbackHandlers;
@@ -244,9 +236,8 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* Set the JAAS login configuration file.
*
* @param loginConfig Spring
- * Resource
- *
+ * href="http://www.springframework.org/docs/api/org/springframework/core/io/Resource.html">Spring
+ * Resource
* @see JAAS
* Reference
@@ -274,7 +265,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
}
public void setLoginExceptionResolver(
- LoginExceptionResolver loginExceptionResolver) {
+ LoginExceptionResolver loginExceptionResolver) {
this.loginExceptionResolver = loginExceptionResolver;
}
@@ -285,38 +276,52 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
public void afterPropertiesSet() throws Exception {
Assert.notNull(loginConfig, "loginConfig must be set on " + getClass());
Assert.hasLength(loginContextName,
- "loginContextName must be set on " + getClass());
+ "loginContextName must be set on " + getClass());
- String loginConfigStr = null;
-
- try {
- loginConfigStr = loginConfig.getFile().toString();
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("Could not resolve loginConfig [" + loginConfig
- + "] as a File, using URL");
- }
-
- loginConfigStr = loginConfig.getURL().toString();
- }
-
- boolean allowed = "true".equalsIgnoreCase(Security.getProperty(
- "policy.allowSystemProperty"));
-
- if (useSystemProperty && allowed) {
- log.debug("Setting system property [" + SYSPROP + "] to: "
- + loginConfigStr);
- System.setProperty(SYSPROP, loginConfigStr);
- } else {
- if (useSystemProperty && !allowed) {
- log.warn("useSystemProperty is true, but the security property 'policy.allowSystemProperty' is false. " +
- "Jaas will be configured using the login.config.url property.");
- }
- setPropertyUsingLoop(loginConfig.getURL().toString());
- }
+ configureJaas(loginConfig);
Assert.notNull(Configuration.getConfiguration(),
- "As per http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/Configuration.html \"If a Configuration object was set via the Configuration.setConfiguration method, then that object is returned. Otherwise, a default Configuration object is returned\". Your JRE returned null to Configuration.getConfiguration().");
+ "As per http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/Configuration.html \"If a Configuration object was set via the Configuration.setConfiguration method, then that object is returned. Otherwise, a default Configuration object is returned\". Your JRE returned null to Configuration.getConfiguration().");
+ }
+
+ /**
+ * Hook method for configuring Jaas
+ *
+ * @param loginConfigStr URL to Jaas login configuration
+ */
+ protected void configureJaas(Resource loginConfig) throws IOException {
+ configureJaasUsingLoop();
+ }
+
+ /**
+ * Loops through the login.config.url.1,login.config.url.2 properties
+ * looking for the login configuration. If it is not set, it will be set
+ * to the last available login.config.url.X property.
+ */
+ private void configureJaasUsingLoop() throws IOException {
+ String loginConfigUrl = loginConfig.getURL().toString();
+ boolean alreadySet = false;
+
+ int n = 1;
+ String prefix = "login.config.url.";
+ String existing = null;
+
+ while ((existing = Security.getProperty(prefix + n)) != null) {
+ alreadySet = existing.equals(loginConfigUrl);
+
+ if (alreadySet) {
+ break;
+ }
+
+ n++;
+ }
+
+ if (!alreadySet) {
+ String key = prefix + n;
+ log.debug("Setting security property [" + key + "] to: "
+ + loginConfigUrl);
+ Security.setProperty(key, loginConfigUrl);
+ }
}
/**
@@ -324,28 +329,26 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* and credential
*
* @param auth The Authentication object to be authenticated.
- *
* @return The authenticated Authentication object, with it's
* grantedAuthorities set.
- *
* @throws AuthenticationException This implementation does not handle
- * 'locked' or 'disabled' accounts. This method only throws a
- * AuthenticationServiceException, with the message of the
- * LoginException that will be thrown, should the
- * loginContext.login() method fail.
+ * 'locked' or 'disabled' accounts. This method only throws a
+ * AuthenticationServiceException, with the message of the
+ * LoginException that will be thrown, should the
+ * loginContext.login() method fail.
*/
public Authentication authenticate(Authentication auth)
- throws AuthenticationException {
+ throws AuthenticationException {
if (auth instanceof UsernamePasswordAuthenticationToken) {
UsernamePasswordAuthenticationToken request = (UsernamePasswordAuthenticationToken) auth;
try {
//Create the LoginContext object, and pass our InternallCallbackHandler
- LoginContext lc = new LoginContext(loginContextName,
+ LoginContext loginContext = new LoginContext(loginContextName,
new InternalCallbackHandler(auth));
//Attempt to login the user, the LoginContext will call our InternalCallbackHandler at this point.
- lc.login();
+ loginContext.login();
//create a set to hold the authorities, and add any that have already been applied.
Set authorities = new HashSet();
@@ -355,10 +358,10 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
}
//get the subject principals and pass them to each of the AuthorityGranters
- Set principals = lc.getSubject().getPrincipals();
+ Set principals = loginContext.getSubject().getPrincipals();
for (Iterator iterator = principals.iterator();
- iterator.hasNext();) {
+ iterator.hasNext();) {
Principal principal = (Principal) iterator.next();
for (int i = 0; i < authorityGranters.length; i++) {
@@ -368,7 +371,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
//If the granter doesn't wish to grant any authorities, it should return null.
if ((roles != null) && !roles.isEmpty()) {
for (Iterator roleIterator = roles.iterator();
- roleIterator.hasNext();) {
+ roleIterator.hasNext();) {
String role = roleIterator.next().toString();
authorities.add(new JaasGrantedAuthority(role,
principal));
@@ -378,10 +381,10 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
}
//Convert the authorities set back to an array and apply it to the token.
- UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(request
+ JaasAuthenticationToken result = new JaasAuthenticationToken(request
.getPrincipal(), request.getCredentials(),
(GrantedAuthority[]) authorities.toArray(
- new GrantedAuthority[authorities.size()]));
+ new GrantedAuthority[authorities.size()]), loginContext);
//Publish the success event
publishSuccessEvent(result);
@@ -390,7 +393,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
return result;
} catch (LoginException loginException) {
AcegiSecurityException ase = loginExceptionResolver
- .resolveException(loginException);
+ .resolveException(loginException);
publishFailureEvent(request, ase);
throw ase;
@@ -404,16 +407,48 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
+ public void onApplicationEvent(ApplicationEvent applicationEvent) {
+ if (applicationEvent instanceof HttpSessionDestroyedEvent) {
+ HttpSessionDestroyedEvent event = (HttpSessionDestroyedEvent) applicationEvent;
+ handleLogout(event);
+ }
+ }
+
+ /**
+ * Handles the logout by getting the SecurityContext for the session that was destroyed.
+ * MUST NOT use SecurityContextHolder we are logging out a session that is not related to the current user.
+ * @param event
+ */
+ protected void handleLogout(HttpSessionDestroyedEvent event) {
+ SecurityContext context = (SecurityContext) event.getSession().getAttribute(HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
+ Authentication auth = context.getAuthentication();
+ if (auth instanceof JaasAuthenticationToken) {
+ JaasAuthenticationToken token = (JaasAuthenticationToken) auth;
+ try {
+ LoginContext loginContext = token.getLoginContext();
+ if (loginContext != null) {
+ log.debug("Logging principal: [" + token.getPrincipal() + "] out of LoginContext");
+ loginContext.logout();
+ } else {
+ log.debug("Cannot logout principal: [" + token.getPrincipal() + "] from LoginContext. " +
+ "The LoginContext is unavailable");
+ }
+ } catch (LoginException e) {
+ log.warn("Error error logging out of LoginContext", e);
+ }
+ }
+ }
+
/**
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden
* by subclasses for different functionality
*
* @param token The {@link UsernamePasswordAuthenticationToken} being
- * processed
+ * processed
* @param ase The {@link AcegiSecurityException} that caused the failure
*/
protected void publishFailureEvent(
- UsernamePasswordAuthenticationToken token, AcegiSecurityException ase) {
+ UsernamePasswordAuthenticationToken token, AcegiSecurityException ase) {
getApplicationContext().publishEvent(new JaasAuthenticationFailedEvent(
token, ase));
}
@@ -423,59 +458,14 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
* by subclasses for different functionality.
*
* @param token The {@link UsernamePasswordAuthenticationToken} being
- * processed
+ * processed
*/
protected void publishSuccessEvent(
- UsernamePasswordAuthenticationToken token) {
+ UsernamePasswordAuthenticationToken token) {
getApplicationContext().publishEvent(new JaasAuthenticationSuccessEvent(
token));
}
- /**
- * Loops through the login.config.url.1,login.config.url.2 properties
- * looking for the login configuration. If it is not set, it will be set
- * to the last available login.config.url.X property.
- *
- * @param loginConfigStr
- */
- private void setPropertyUsingLoop(String loginConfigStr) {
- boolean alreadySet = false;
-
- int n = 1;
- String prefix = "login.config.url.";
- String existing = null;
-
- while ((existing = Security.getProperty(prefix + n)) != null) {
- alreadySet = existing.equals(loginConfigStr);
-
- if (alreadySet) {
- break;
- }
-
- n++;
- }
-
- if (!alreadySet) {
- String key = prefix + n;
- log.debug("Setting security property [" + key + "] to: "
- + loginConfigStr);
- Security.setProperty(key, loginConfigStr);
- }
- }
-
- public boolean isUseSystemProperty() {
- return useSystemProperty;
- }
-
- /**
- * If true, the JaasAuthenticationProvider will configure Jaas using the system property 'java.security.auth.login.config'.
- * If false, the JaasAuthenticationProvider will configure Jaas using the 'login.config.url.x' property.
- *
Default:True
- * @param useSystemProperty
- */
- public void setUseSystemProperty(boolean useSystemProperty) {
- this.useSystemProperty = useSystemProperty;
- }
//~ Inner Classes ==========================================================
/**
@@ -489,7 +479,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider,
}
public void handle(Callback[] callbacks)
- throws IOException, UnsupportedCallbackException {
+ throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbackHandlers.length; i++) {
JaasAuthenticationCallbackHandler handler = callbackHandlers[i];
diff --git a/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationToken.java
new file mode 100644
index 0000000000..86df5543ef
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/jaas/JaasAuthenticationToken.java
@@ -0,0 +1,31 @@
+package net.sf.acegisecurity.providers.jaas;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import net.sf.acegisecurity.GrantedAuthority;
+
+import javax.security.auth.login.LoginContext;
+
+/**
+ * UsernamePasswordAuthenticationToken extension to carry the Jaas LoginContext that the user was logged into
+ * @author Ray Krueger
+ */
+public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken {
+
+ private transient LoginContext loginContext = null;
+
+ public JaasAuthenticationToken(Object principal, Object credentials, LoginContext loginContext) {
+ super(principal, credentials);
+ this.loginContext = loginContext;
+ }
+
+ public JaasAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities, LoginContext loginContext) {
+ super(principal, credentials, authorities);
+ this.loginContext = loginContext;
+ }
+
+ public LoginContext getLoginContext() {
+ return loginContext;
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/jaas/JaasAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/jaas/JaasAuthenticationProviderTests.java
index 4244510844..d53a1d4344 100644
--- a/core/src/test/java/org/acegisecurity/providers/jaas/JaasAuthenticationProviderTests.java
+++ b/core/src/test/java/org/acegisecurity/providers/jaas/JaasAuthenticationProviderTests.java
@@ -17,12 +17,17 @@ package net.sf.acegisecurity.providers.jaas;
import junit.framework.TestCase;
import net.sf.acegisecurity.*;
+import net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter;
+import net.sf.acegisecurity.context.SecurityContextImpl;
+import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.mock.web.MockHttpSession;
import javax.security.auth.login.LoginException;
+import javax.security.auth.login.LoginContext;
import java.net.URL;
import java.security.Security;
import java.util.Arrays;
@@ -74,7 +79,6 @@ public class JaasAuthenticationProviderTests extends TestCase {
String resName = "/" + getClass().getName().replace('.', '/') + ".conf";
URL url = getClass().getResource(resName);
- Security.setProperty("policy.allowSystemProperty", "false");
Security.setProperty("login.config.url.1", url.toString());
setUp();
@@ -211,10 +215,40 @@ public class JaasAuthenticationProviderTests extends TestCase {
new GrantedAuthority[]{})));
}
+ public void testLogout() throws Exception {
+
+ MockLoginContext loginContext = new MockLoginContext(jaasProvider.getLoginContextName());
+
+ JaasAuthenticationToken token = new JaasAuthenticationToken(null, null, loginContext);
+
+ SecurityContextImpl context = new SecurityContextImpl();
+ context.setAuthentication(token);
+
+ MockHttpSession mockSession = new MockHttpSession();
+ mockSession.setAttribute(HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY, context);
+
+ jaasProvider.onApplicationEvent(new HttpSessionDestroyedEvent(mockSession));
+
+ assertTrue(loginContext.loggedOut);
+ }
+
protected void setUp() throws Exception {
String resName = "/" + getClass().getName().replace('.', '/') + ".xml";
context = new ClassPathXmlApplicationContext(resName);
eventCheck = (JaasEventCheck) context.getBean("eventCheck");
jaasProvider = (JaasAuthenticationProvider) context.getBean("jaasAuthenticationProvider");
}
+
+ private static class MockLoginContext extends LoginContext {
+
+ boolean loggedOut = false;
+
+ public MockLoginContext(String loginModule) throws LoginException {
+ super(loginModule);
+ }
+
+ public void logout() throws LoginException {
+ this.loggedOut = true;
+ }
+ }
}