diff --git a/core/src/main/java/org/acegisecurity/providers/jaas/SecureContextLoginModule.java b/core/src/main/java/org/acegisecurity/providers/jaas/SecureContextLoginModule.java
new file mode 100644
index 0000000000..614759f164
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/jaas/SecureContextLoginModule.java
@@ -0,0 +1,174 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.providers.jaas;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.context.ContextHolder;
+import net.sf.acegisecurity.context.security.SecureContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+
+/**
+ * An implementation of {@link LoginModule} that uses an Acegi Security {@link
+ * SecureContext} to provide authentication.
+ * This LoginModule provides opposite functionality to the {@link
+ * JaasAuthenticationProvider} API, and should not really be used in
+ * conjunction.
+ * The {@link JaasAuthenticationProvider} allows Acegi to authenticate against
+ * Jaas.
+ * The SecureContextLoginModule allows a Jaas based application to authenticate
+ * against Acegi.
+ *
+ * @author Brian Moseley
+ * @author Ray Krueger
+ */
+public class SecureContextLoginModule implements LoginModule {
+ //~ Static fields/initializers =============================================
+
+ private static final Log log = LogFactory.getLog(SecureContextLoginModule.class);
+
+ //~ Instance fields ========================================================
+
+ private Authentication authen;
+ private Subject subject;
+
+ //~ Methods ================================================================
+
+ /**
+ * Abort the authentication process by forgetting the Acegi Security
+ * Authentication.
+ *
+ * @return true if this method succeeded, or false if this
+ * LoginModule should be ignored.
+ *
+ * @exception LoginException if the abort fails
+ */
+ public boolean abort() throws LoginException {
+ if (authen == null) {
+ return false;
+ }
+
+ authen = null;
+
+ return true;
+ }
+
+ /**
+ * Authenticate the Subject (phase two) by adding the Acegi
+ * Security Authentication to the Subject's
+ * principals.
+ *
+ * @return true if this method succeeded, or false if this
+ * LoginModule should be ignored.
+ *
+ * @exception LoginException if the commit fails
+ */
+ public boolean commit() throws LoginException {
+ if (authen == null) {
+ return false;
+ }
+
+ subject.getPrincipals().add(authen);
+
+ return true;
+ }
+
+ /**
+ * Initialize this LoginModule. Ignores the callback handler,
+ * since the code establishing the LoginContext likely won't
+ * provide one that understands Acegi Security. Also ignores the
+ * sharedState and options parameters, since
+ * none are recognized.
+ *
+ * @param subject the Subject to be authenticated.
+ * @param callbackHandler is ignored
+ * @param sharedState is ignored
+ * @param options are ignored
+ */
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map sharedState, Map options) {
+ this.subject = subject;
+ }
+
+ /**
+ * Authenticate the Subject (phase one) by extracting the
+ * Acegi Security Authentication from the current
+ * SecureContext.
+ *
+ * @return true if the authentication succeeded, or false if this
+ * LoginModule should be ignored.
+ *
+ * @throws LoginException if the authentication fails
+ */
+ public boolean login() throws LoginException {
+ if (ContextHolder.getContext() == null) {
+ log.debug("no security context found");
+ return false;
+ }
+
+ if (!(ContextHolder.getContext() instanceof SecureContext)) {
+ log.debug("security context not instance of SecureContext");
+
+ return false;
+ }
+
+ SecureContext context = (SecureContext) ContextHolder.getContext();
+ authen = context.getAuthentication();
+
+ if (authen == null) {
+ throw new LoginException("Authentication not found in security"
+ + " context");
+ }
+
+ return true;
+ }
+
+ /**
+ * Log out the Subject.
+ *
+ * @return true if this method succeeded, or false if this
+ * LoginModule should be ignored.
+ *
+ * @exception LoginException if the logout fails
+ */
+ public boolean logout() throws LoginException {
+ if (authen == null) {
+ return false;
+ }
+
+ subject.getPrincipals().remove(authen);
+ authen = null;
+
+ return true;
+ }
+
+ Authentication getAuthentication() {
+ return authen;
+ }
+
+ Subject getSubject() {
+ return subject;
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/jaas/SecureContextLoginModuleTest.java b/core/src/test/java/org/acegisecurity/providers/jaas/SecureContextLoginModuleTest.java
new file mode 100644
index 0000000000..56f17522ff
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/providers/jaas/SecureContextLoginModuleTest.java
@@ -0,0 +1,108 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.providers.jaas;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.context.ContextHolder;
+import net.sf.acegisecurity.context.ContextImpl;
+import net.sf.acegisecurity.context.security.SecureContextImpl;
+import net.sf.acegisecurity.context.security.SecureContext;
+import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+import java.util.HashSet;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author Ray Krueger
+ */
+public class SecureContextLoginModuleTest extends TestCase {
+ //~ Instance fields ========================================================
+
+ private SecureContextLoginModule module = null;
+ private Subject subject = new Subject(false, new HashSet(), new HashSet(),
+ new HashSet());
+ private UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("principal", "credentials");
+
+ //~ Methods ================================================================
+
+ public void testLoginException() throws Exception {
+ try {
+ module.login();
+ fail("LoginException expected, there is no Authentication in the SecureContext");
+ } catch (LoginException e) {
+ }
+ }
+
+ public void testLoginSuccess() throws Exception {
+ SecureContext sc = (SecureContext) ContextHolder.getContext();
+ sc.setAuthentication(auth);
+ assertTrue("Login should succeed, there is an authentication set", module.login());
+ assertTrue("The authentication is not null, this should return true", module.commit());
+ assertTrue("Principals should contain the authentication", subject.getPrincipals().contains(auth));
+ }
+
+ public void testNoContext() throws Exception {
+ ContextHolder.setContext(null);
+ assertFalse("Should return false and ask to be ignored", module.login());
+ }
+
+ public void testUnsupportedContext() throws Exception {
+ ContextHolder.setContext(new ContextImpl());
+ assertFalse("Should return false and ask to be ignored", module.login());
+ }
+
+ public void testLogout() throws Exception {
+ SecureContext sc = (SecureContext) ContextHolder.getContext();
+ sc.setAuthentication(auth);
+ module.login();
+ assertTrue("Should return true as it succeeds", module.logout());
+ assertEquals("Authentication should be null", null, module.getAuthentication());
+
+ assertFalse("Principals should not contain the authentication after logout", subject.getPrincipals().contains(auth));
+ }
+
+ public void testNullLogout() throws Exception {
+ assertFalse(module.logout());
+ }
+
+ public void testAbort() throws Exception {
+ assertFalse("Should return false, no auth is set", module.abort());
+ SecureContext sc = (SecureContext) ContextHolder.getContext();
+ sc.setAuthentication(auth);
+ module.login();
+ module.commit();
+ assertTrue(module.abort());
+ }
+
+ protected void setUp() throws Exception {
+ module = new SecureContextLoginModule();
+
+ module.initialize(subject, null, null, null);
+
+ ContextHolder.setContext(new SecureContextImpl());
+ }
+
+ protected void tearDown() throws Exception {
+ ContextHolder.setContext(null);
+ module = null;
+ }
+}