Browse Source

SEC-1564: JAAS Configuration can now be injected into DefaultJaasAuthenticationProvider

rwinch 15 years ago
parent
commit
58d9903ebc
19 changed files with 1486 additions and 297 deletions
  1. 2 1
      core/core.gradle
  2. 365 0
      core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java
  3. 123 0
      core/src/main/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProvider.java
  4. 17 243
      core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java
  5. 102 0
      core/src/main/java/org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.java
  6. 4 0
      core/src/main/java/org/springframework/security/authentication/jaas/memory/package-info.java
  7. 273 0
      core/src/test/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.java
  8. 98 0
      core/src/test/java/org/springframework/security/authentication/jaas/memory/InMemoryConfigurationTests.java
  9. 41 0
      core/src/test/resources/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.xml
  10. 136 52
      docs/manual/src/docbook/jaas-auth-provider.xml
  11. 6 0
      docs/manual/src/docbook/samples.xml
  12. 25 0
      samples/jaas/jaas.gradle
  13. 34 0
      samples/jaas/src/main/java/samples/jaas/RoleUserAuthorityGranter.java
  14. 85 0
      samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java
  15. 52 0
      samples/jaas/src/main/resources/applicationContext-security.xml
  16. 63 0
      samples/jaas/src/main/webapp/WEB-INF/web.xml
  17. 18 0
      samples/jaas/src/main/webapp/index.jsp
  18. 40 0
      samples/jaas/src/main/webapp/secure/index.jsp
  19. 2 1
      settings.gradle

+ 2 - 1
core/core.gradle

@@ -14,7 +14,8 @@ dependencies {
             'javax.annotation:jsr250-api:1.0'
 
     testCompile 'commons-collections:commons-collections:3.2',
-                "org.springframework:spring-test:$springVersion"
+                "org.springframework:spring-test:$springVersion",
+                "ch.qos.logback:logback-classic:$logbackVersion"
 
     testRuntime "hsqldb:hsqldb:$hsqlVersion"
 }

+ 365 - 0
core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java

@@ -0,0 +1,365 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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.springframework.security.authentication.jaas;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashSet;
+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.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.context.ApplicationListener;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent;
+import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.session.SessionDestroyedEvent;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * An {@link AuthenticationProvider} implementation that retrieves user details from a JAAS login configuration.
+ *
+ * <p>This <code>AuthenticationProvider</code> is capable of validating {@link
+ * org.springframework.security.authentication.UsernamePasswordAuthenticationToken} requests contain the correct username and
+ * password.</p>
+ * <p>This implementation is backed by a <a
+ * href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jaas/JAASRefGuide.html">JAAS</a> configuration that is provided by
+ * a subclass's implementation of {@link #createLoginContext(CallbackHandler)}.
+ * 
+ * <p>When using JAAS login modules as the authentication source, sometimes the
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/LoginContext.html">LoginContext</a> will
+ * require <i>CallbackHandler</i>s. The AbstractJaasAuthenticationProvider uses an internal
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/callback/CallbackHandler.html">CallbackHandler
+ * </a> to wrap the {@link JaasAuthenticationCallbackHandler}s configured in the ApplicationContext.
+ * When the LoginContext calls the internal CallbackHandler, control is passed to each
+ * {@link JaasAuthenticationCallbackHandler} for each Callback passed.
+ * </p>
+ * <p>{@link JaasAuthenticationCallbackHandler}s are passed to the AbstractJaasAuthenticationProvider through the {@link
+ * #setCallbackHandlers(org.springframework.security.authentication.jaas.JaasAuthenticationCallbackHandler[]) callbackHandlers}
+ * property.
+ * <pre>
+ * &lt;property name="callbackHandlers"&gt;
+ *   &lt;list&gt;
+ *     &lt;bean class="org.springframework.security.authentication.jaas.TestCallbackHandler"/&gt;
+ *     &lt;bean class="{@link JaasNameCallbackHandler org.springframework.security.authentication.jaas.JaasNameCallbackHandler}"/&gt;
+ *     &lt;bean class="{@link JaasPasswordCallbackHandler org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler}"/&gt;
+ *  &lt;/list&gt;
+ * &lt;/property&gt;
+ * </pre>
+ * </p>
+ * <p>
+ * After calling LoginContext.login(), the AbstractJaasAuthenticationProvider will retrieve the returned Principals
+ * from the Subject (LoginContext.getSubject().getPrincipals). Each returned principal is then passed to the
+ * configured {@link AuthorityGranter}s. An AuthorityGranter is a mapping between a returned Principal, and a role
+ * name. If an AuthorityGranter wishes to grant an Authorization a role, it returns that role name from it's {@link
+ * AuthorityGranter#grant(java.security.Principal)} method. The returned role will be applied to the Authorization
+ * object as a {@link GrantedAuthority}.</p>
+ * <p>AuthorityGranters are configured in spring xml as follows...
+ * <pre>
+ * &lt;property name="authorityGranters"&gt;
+ *   &lt;list&gt;
+ *     &lt;bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/&gt;
+ *   &lt;/list&gt;
+ *  &lt;/property&gt;
+ * </pre>
+ * 
+ * @author Ray Krueger
+ * @author Rob Winch
+ */
+public abstract class AbstractJaasAuthenticationProvider implements AuthenticationProvider,
+ApplicationEventPublisherAware, InitializingBean, ApplicationListener<SessionDestroyedEvent> {
+    //~ Instance fields ================================================================================================
+
+    private ApplicationEventPublisher applicationEventPublisher;
+    private AuthorityGranter[] authorityGranters;
+    private JaasAuthenticationCallbackHandler[] callbackHandlers;
+    protected final Log log = LogFactory.getLog(getClass());
+    private LoginExceptionResolver loginExceptionResolver = new DefaultLoginExceptionResolver();
+    private String loginContextName = "SPRINGSECURITY";
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Validates the required properties are set. In addition, if
+     * {@link #setCallbackHandlers(JaasAuthenticationCallbackHandler[])} has not
+     * been called with valid handlers, initializes to use
+     * {@link JaasNameCallbackHandler} and {@link JaasPasswordCallbackHandler}.
+     */
+    public void afterPropertiesSet() throws Exception {
+        Assert.hasLength(loginContextName, "loginContextName cannot be null or empty");
+        Assert.notEmpty(authorityGranters, "authorityGranters cannot be null or empty");
+        if (ObjectUtils.isEmpty(callbackHandlers)) {
+            setCallbackHandlers(new JaasAuthenticationCallbackHandler[] { new JaasNameCallbackHandler(),
+                    new JaasPasswordCallbackHandler() });
+        }
+        Assert.notNull(loginExceptionResolver, "loginExceptionResolver cannot be null");
+    }
+
+    /**
+     * Attempts to login the user given the Authentication objects principal 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.
+     */
+    public Authentication authenticate(Authentication auth) throws AuthenticationException {
+        if (!(auth instanceof UsernamePasswordAuthenticationToken)) {
+            return null;
+        }
+
+        UsernamePasswordAuthenticationToken request = (UsernamePasswordAuthenticationToken) auth;
+        Set<GrantedAuthority> authorities;
+
+        try {
+            // Create the LoginContext object, and pass our InternallCallbackHandler
+            LoginContext loginContext = createLoginContext(new InternalCallbackHandler(auth));
+
+            // Attempt to login the user, the LoginContext will call our InternalCallbackHandler at this point.
+            loginContext.login();
+
+            // Create a set to hold the authorities, and add any that have already been applied.
+            authorities = new HashSet<GrantedAuthority>();
+            authorities.addAll(request.getAuthorities());
+
+            // Get the subject principals and pass them to each of the AuthorityGranters
+            Set<Principal> principals = loginContext.getSubject().getPrincipals();
+
+            for (Principal principal : principals) {
+                for (AuthorityGranter granter : authorityGranters) {
+                    Set<String> roles = granter.grant(principal);
+
+                    // If the granter doesn't wish to grant any authorities, it should return null.
+                    if ((roles != null) && !roles.isEmpty()) {
+                        for (String role : roles) {
+                            authorities.add(new JaasGrantedAuthority(role, principal));
+                        }
+                    }
+                }
+            }
+
+            //Convert the authorities set back to an array and apply it to the token.
+            JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
+                    request.getCredentials(), new ArrayList<GrantedAuthority>(authorities), loginContext);
+
+            //Publish the success event
+            publishSuccessEvent(result);
+
+            //we're done, return the token.
+            return result;
+
+        } catch (LoginException loginException) {
+            AuthenticationException ase = loginExceptionResolver.resolveException(loginException);
+
+            publishFailureEvent(request, ase);
+            throw ase;
+        }
+    }
+
+    /**
+     * Creates the LoginContext to be used for authentication.
+     * 
+     * @param handler The CallbackHandler that should be used for the LoginContext (never <code>null</code>).
+     * @return the LoginContext to use for authentication.
+     * @throws LoginException
+     */
+    protected abstract LoginContext createLoginContext(CallbackHandler handler) throws LoginException;
+
+    /**
+     * Handles the logout by getting the SecurityContext for the session that was destroyed. <b>MUST NOT use
+     * SecurityContextHolder as we are logging out a session that is not related to the current user.</b>
+     *
+     * @param event
+     */
+    protected void handleLogout(SessionDestroyedEvent event) {
+        SecurityContext context = event.getSecurityContext();
+
+        if (context == null) {
+            log.debug("The destroyed session has no SecurityContext");
+
+            return;
+        }
+
+        Authentication auth = context.getAuthentication();
+
+        if ((auth != null) && (auth instanceof JaasAuthenticationToken)) {
+            JaasAuthenticationToken token = (JaasAuthenticationToken) auth;
+
+            try {
+                LoginContext loginContext = token.getLoginContext();
+                boolean debug = log.isDebugEnabled();
+                if (loginContext != null) {
+                    if (debug) {
+                        log.debug("Logging principal: [" + token.getPrincipal() + "] out of LoginContext");
+                    }
+                    loginContext.logout();
+                } else if (debug) {
+                    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);
+            }
+        }
+    }
+
+    public void onApplicationEvent(SessionDestroyedEvent event) {
+        handleLogout(event);
+    }
+
+    /**
+     * Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by subclasses for different
+     * functionality
+     *
+     * @param token The authentication token being processed
+     * @param ase The excetion that caused the authentication failure
+     */
+    protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) {
+        if (applicationEventPublisher != null) {
+            applicationEventPublisher.publishEvent(new JaasAuthenticationFailedEvent(token, ase));
+        }
+    }
+
+    /**
+     * Publishes the {@link JaasAuthenticationSuccessEvent}. Can be overridden by subclasses for different
+     * functionality.
+     *
+     * @param token The token being processed
+     */
+    protected void publishSuccessEvent(UsernamePasswordAuthenticationToken token) {
+        if (applicationEventPublisher != null) {
+            applicationEventPublisher.publishEvent(new JaasAuthenticationSuccessEvent(token));
+        }
+    }
+
+    /**
+     * Returns the AuthorityGrannter array that was passed to the {@link
+     * #setAuthorityGranters(AuthorityGranter[])} method, or null if it none were ever set.
+     *
+     * @return The AuthorityGranter array, or null
+     *
+     * @see #setAuthorityGranters(AuthorityGranter[])
+     */
+    AuthorityGranter[] getAuthorityGranters() {
+        return authorityGranters;
+    }
+
+    /**
+     * Set the AuthorityGranters that should be consulted for role names to be granted to the Authentication.
+     *
+     * @param authorityGranters AuthorityGranter array
+     *
+     * @see JaasAuthenticationProvider
+     */
+    public void setAuthorityGranters(AuthorityGranter[] authorityGranters) {
+        this.authorityGranters = authorityGranters;
+    }
+
+    /**
+     * Returns the current JaasAuthenticationCallbackHandler array, or null if none are set.
+     *
+     * @return the JAASAuthenticationCallbackHandlers.
+     *
+     * @see #setCallbackHandlers(JaasAuthenticationCallbackHandler[])
+     */
+    JaasAuthenticationCallbackHandler[] getCallbackHandlers() {
+        return callbackHandlers;
+    }
+
+    /**
+     * Set the JAASAuthentcationCallbackHandler array to handle callback objects generated by the
+     * LoginContext.login method.
+     *
+     * @param callbackHandlers Array of JAASAuthenticationCallbackHandlers
+     */
+    public void setCallbackHandlers(JaasAuthenticationCallbackHandler[] callbackHandlers) {
+        this.callbackHandlers = callbackHandlers;
+    }
+
+    String getLoginContextName() {
+        return loginContextName;
+    }
+
+    /**
+     * Set the loginContextName, this name is used as the index to the configuration specified in the
+     * loginConfig property.
+     *
+     * @param loginContextName
+     */
+    public void setLoginContextName(String loginContextName) {
+        this.loginContextName = loginContextName;
+    }
+
+    LoginExceptionResolver getLoginExceptionResolver() {
+        return loginExceptionResolver;
+    }
+
+    public void setLoginExceptionResolver(LoginExceptionResolver loginExceptionResolver) {
+        this.loginExceptionResolver = loginExceptionResolver;
+    }
+
+    public boolean supports(Class<?> aClass) {
+        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
+    }
+
+    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+        this.applicationEventPublisher = applicationEventPublisher;
+    }
+
+    protected ApplicationEventPublisher getApplicationEventPublisher() {
+        return applicationEventPublisher;
+    }
+
+    //~ Inner Classes ==================================================================================================
+
+    /**
+     * Wrapper class for JAASAuthenticationCallbackHandlers
+     */
+    private class InternalCallbackHandler implements CallbackHandler {
+        private final Authentication authentication;
+
+        public InternalCallbackHandler(Authentication authentication) {
+            this.authentication = authentication;
+        }
+
+        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+            for (JaasAuthenticationCallbackHandler handler : callbackHandlers) {
+                for (Callback callback : callbacks) {
+                    handler.handle(callback, authentication);
+                }
+            }
+        }
+    }
+}

+ 123 - 0
core/src/main/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProvider.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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.springframework.security.authentication.jaas;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.springframework.security.authentication.jaas.memory.InMemoryConfiguration;
+import org.springframework.util.Assert;
+
+/**
+ * <p>
+ * Creates a LoginContext using the Configuration provided to it. This allows
+ * the configuration to be injected regardless of the value of
+ * {@link Configuration#getConfiguration()}.
+ * </p>
+ * <p>
+ * While not bound to any particular Configuration implementation, an in memory version of a JAAS
+ * configuration can be represented using {@link InMemoryConfiguration}.
+ * </p>
+ * <p>
+ * The following JAAS configuration:
+ * </p>
+ * 
+ * <pre>
+ * SPRINGSECURITY {
+ *    sample.SampleLoginModule required;
+ *  };
+ * </pre>
+ * 
+ * <p>
+ * Can be represented as follows:
+ * </p>
+ * 
+ * <pre>
+ * &lt;bean id=&quot;jaasAuthProvider&quot; class=&quot;org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider&quot;&gt;
+ *   &lt;property name=&quot;configuration&quot;&gt;
+ *     &lt;bean class=&quot;org.springframework.security.authentication.jaas.memory.InMemoryConfiguration&quot;&gt;
+ *       &lt;constructor-arg&gt;
+ *         &lt;map&gt;
+ *           &lt;!-- SPRINGSECURITY is the default loginContextName for AbstractJaasAuthenticationProvider--&gt;
+ *           &lt;entry key=&quot;SPRINGSECURITY&quot;&gt;
+ *             &lt;array&gt;
+ *               &lt;bean class=&quot;javax.security.auth.login.AppConfigurationEntry&quot;&gt;
+ *                 &lt;constructor-arg value=&quot;sample.SampleLoginModule&quot; /&gt;
+ *                 &lt;constructor-arg&gt;
+ *                   &lt;util:constant static-field=&quot;javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED&quot; /&gt;
+ *                 &lt;/constructor-arg&gt;
+ *                 &lt;constructor-arg&gt;
+ *                   &lt;map&gt;&lt;/map&gt;
+ *                 &lt;/constructor-arg&gt;
+ *               &lt;/bean&gt;
+ *             &lt;/array&gt;
+ *           &lt;/entry&gt;
+ *         &lt;/map&gt;
+ *       &lt;/constructor-arg&gt;
+ *     &lt;/bean&gt;
+ *   &lt;/property&gt;
+ *   &lt;property name=&quot;authorityGranters&quot;&gt;
+ *     &lt;list&gt;
+ *       &lt;!-- You will need to write your own implementation of AuthorityGranter --&gt;
+ *       &lt;bean class=&quot;org.springframework.security.authentication.jaas.TestAuthorityGranter&quot;/&gt;
+ *     &lt;/list&gt;
+ *   &lt;/property&gt;
+ * &lt;/bean&gt;
+ * </pre>
+ * 
+ * @author Rob Winch
+ * @see AbstractJaasAuthenticationProvider
+ * @see InMemoryConfiguration
+ */
+public class DefaultJaasAuthenticationProvider extends AbstractJaasAuthenticationProvider {
+    //~ Instance fields ================================================================================================
+
+    private Configuration configuration;
+
+    //~ Methods ========================================================================================================
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        super.afterPropertiesSet();
+        Assert.notNull(configuration, "configuration cannot be null.");
+    }
+
+    /**
+     * Creates a LoginContext using the Configuration that was specified in
+     * {@link #setConfiguration(Configuration)}.
+     */
+    @Override
+    protected LoginContext createLoginContext(CallbackHandler handler) throws LoginException {
+        return new LoginContext(getLoginContextName(), null, handler, getConfiguration());
+    }
+
+    protected Configuration getConfiguration() {
+        return configuration;
+    }
+
+    /**
+     * Sets the Configuration to use for Authentication. 
+     * 
+     * @param configuration
+     *            the Configuration that is used when
+     *            {@link #createLoginContext(CallbackHandler)} is called.
+     */
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+}

+ 17 - 243
core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java

@@ -18,35 +18,21 @@ package org.springframework.security.authentication.jaas;
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
-import java.security.Principal;
 import java.security.Security;
-import java.util.ArrayList;
-import java.util.HashSet;
-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 org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.context.ApplicationEventPublisherAware;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.io.Resource;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent;
-import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
-import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.core.session.SessionDestroyedEvent;
 import org.springframework.util.Assert;
 
 
@@ -125,100 +111,38 @@ import org.springframework.util.Assert;
  * </p>
  *
  * @author Ray Krueger
+ * @author Rob Winch
  */
-public class JaasAuthenticationProvider implements AuthenticationProvider, ApplicationEventPublisherAware,
-        InitializingBean, ApplicationListener<SessionDestroyedEvent> {
+public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvider {
     //~ Static fields/initializers =====================================================================================
 
+    // exists for passivity
     protected static final Log log = LogFactory.getLog(JaasAuthenticationProvider.class);
 
     //~ Instance fields ================================================================================================
 
-    private LoginExceptionResolver loginExceptionResolver = new DefaultLoginExceptionResolver();
     private Resource loginConfig;
-    private String loginContextName = "SPRINGSECURITY";
-    private AuthorityGranter[] authorityGranters;
-    private JaasAuthenticationCallbackHandler[] callbackHandlers;
-    private ApplicationEventPublisher applicationEventPublisher;
     private boolean refreshConfigurationOnStartup = true;
 
     //~ Methods ========================================================================================================
 
     public void afterPropertiesSet() throws Exception {
+        // the superclass is not called because it does additional checks that are non-passive
+        Assert.hasLength(getLoginContextName(), "loginContextName must be set on " + getClass());
         Assert.notNull(loginConfig, "loginConfig must be set on " + getClass());
-        Assert.hasLength(loginContextName, "loginContextName must be set on " + getClass());
-
         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().");
+        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().");
     }
 
-    /**
-     * Attempts to login the user given the Authentication objects principal 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.
-     */
-    public Authentication authenticate(Authentication auth) throws AuthenticationException {
-        if (!(auth instanceof UsernamePasswordAuthenticationToken)) {
-            return null;
-        }
-
-        UsernamePasswordAuthenticationToken request = (UsernamePasswordAuthenticationToken) auth;
-        Set<GrantedAuthority> authorities;
-
-        try {
-            // Create the LoginContext object, and pass our InternallCallbackHandler
-            LoginContext loginContext = new LoginContext(loginContextName, new InternalCallbackHandler(auth));
-
-            // Attempt to login the user, the LoginContext will call our InternalCallbackHandler at this point.
-            loginContext.login();
-
-            // Create a set to hold the authorities, and add any that have already been applied.
-            authorities = new HashSet<GrantedAuthority>();
-            authorities.addAll(request.getAuthorities());
-
-            // Get the subject principals and pass them to each of the AuthorityGranters
-            Set<Principal> principals = loginContext.getSubject().getPrincipals();
-
-            for (Principal principal : principals) {
-                for (AuthorityGranter granter : authorityGranters) {
-                    Set<String> roles = granter.grant(principal);
-
-                    // If the granter doesn't wish to grant any authorities, it should return null.
-                    if ((roles != null) && !roles.isEmpty()) {
-                        for (String role : roles) {
-                            authorities.add(new JaasGrantedAuthority(role, principal));
-                        }
-                    }
-                }
-            }
-
-            //Convert the authorities set back to an array and apply it to the token.
-            JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
-                    request.getCredentials(), new ArrayList<GrantedAuthority>(authorities), loginContext);
-
-            //Publish the success event
-            publishSuccessEvent(result);
-
-            //we're done, return the token.
-            return result;
-
-        } catch (LoginException loginException) {
-            AuthenticationException ase = loginExceptionResolver.resolveException(loginException);
-
-            publishFailureEvent(request, ase);
-            throw ase;
-        }
+    @Override
+    protected LoginContext createLoginContext(CallbackHandler handler) throws LoginException {
+        return new LoginContext(getLoginContextName(), handler);
     }
 
     /**
@@ -277,47 +201,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
 
         return new URL("file", "", loginConfigPath).toString();
     }
-
-    /**
-     * Handles the logout by getting the SecurityContext for the session that was destroyed. <b>MUST NOT use
-     * SecurityContextHolder as we are logging out a session that is not related to the current user.</b>
-     *
-     * @param event
-     */
-    protected void handleLogout(SessionDestroyedEvent event) {
-        SecurityContext context = event.getSecurityContext();
-
-        if (context == null) {
-            log.debug("The destroyed session has no SecurityContext");
-
-            return;
-        }
-
-        Authentication auth = context.getAuthentication();
-
-        if ((auth != null) && (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);
-            }
-        }
-    }
-
-    public void onApplicationEvent(SessionDestroyedEvent event) {
-        handleLogout(event);
-    }
-
+    
     /**
      * Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by subclasses for different
      * functionality
@@ -326,63 +210,8 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
      * @param ase The excetion that caused the authentication failure
      */
     protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) {
-        applicationEventPublisher.publishEvent(new JaasAuthenticationFailedEvent(token, ase));
-    }
-
-    /**
-     * Publishes the {@link JaasAuthenticationSuccessEvent}. Can be overridden by subclasses for different
-     * functionality.
-     *
-     * @param token The token being processed
-     */
-    protected void publishSuccessEvent(UsernamePasswordAuthenticationToken token) {
-        if (applicationEventPublisher != null) {
-            applicationEventPublisher.publishEvent(new JaasAuthenticationSuccessEvent(token));
-        }
-    }
-
-    /**
-     * Returns the AuthorityGrannter array that was passed to the {@link
-     * #setAuthorityGranters(AuthorityGranter[])} method, or null if it none were ever set.
-     *
-     * @return The AuthorityGranter array, or null
-     *
-     * @see #setAuthorityGranters(AuthorityGranter[])
-     */
-    AuthorityGranter[] getAuthorityGranters() {
-        return authorityGranters;
-    }
-
-    /**
-     * Set the AuthorityGranters that should be consulted for role names to be granted to the Authentication.
-     *
-     * @param authorityGranters AuthorityGranter array
-     *
-     * @see JaasAuthenticationProvider
-     */
-    public void setAuthorityGranters(AuthorityGranter[] authorityGranters) {
-        this.authorityGranters = authorityGranters;
-    }
-
-    /**
-     * Returns the current JaasAuthenticationCallbackHandler array, or null if none are set.
-     *
-     * @return the JAASAuthenticationCallbackHandlers.
-     *
-     * @see #setCallbackHandlers(JaasAuthenticationCallbackHandler[])
-     */
-    JaasAuthenticationCallbackHandler[] getCallbackHandlers() {
-        return callbackHandlers;
-    }
-
-    /**
-     * Set the JAASAuthentcationCallbackHandler array to handle callback objects generated by the
-     * LoginContext.login method.
-     *
-     * @param callbackHandlers Array of JAASAuthenticationCallbackHandlers
-     */
-    public void setCallbackHandlers(JaasAuthenticationCallbackHandler[] callbackHandlers) {
-        this.callbackHandlers = callbackHandlers;
+    	// exists for passivity (the superclass does a null check before publishing)
+        getApplicationEventPublisher().publishEvent(new JaasAuthenticationFailedEvent(token, ase));
     }
 
     public Resource getLoginConfig() {
@@ -400,28 +229,6 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
         this.loginConfig = loginConfig;
     }
 
-    String getLoginContextName() {
-        return loginContextName;
-    }
-
-    /**
-     * Set the loginContextName, this name is used as the index to the configuration specified in the
-     * loginConfig property.
-     *
-     * @param loginContextName
-     */
-    public void setLoginContextName(String loginContextName) {
-        this.loginContextName = loginContextName;
-    }
-
-    LoginExceptionResolver getLoginExceptionResolver() {
-        return loginExceptionResolver;
-    }
-
-    public void setLoginExceptionResolver(LoginExceptionResolver loginExceptionResolver) {
-        this.loginExceptionResolver = loginExceptionResolver;
-    }
-
     /**
      * If set, a call to {@code Configuration#refresh()} will be made by {@code #configureJaas(Resource) }
      * method. Defaults to {@code true}.
@@ -434,37 +241,4 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
     public void setRefreshConfigurationOnStartup(boolean refresh) {
         this.refreshConfigurationOnStartup = refresh;
     }
-
-    public boolean supports(Class<?> aClass) {
-        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
-    }
-
-    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
-        this.applicationEventPublisher = applicationEventPublisher;
-    }
-
-    protected ApplicationEventPublisher getApplicationEventPublisher() {
-        return applicationEventPublisher;
-    }
-
-    //~ Inner Classes ==================================================================================================
-
-    /**
-     * Wrapper class for JAASAuthenticationCallbackHandlers
-     */
-    private class InternalCallbackHandler implements CallbackHandler {
-        private final Authentication authentication;
-
-        public InternalCallbackHandler(Authentication authentication) {
-            this.authentication = authentication;
-        }
-
-        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-            for (JaasAuthenticationCallbackHandler handler : callbackHandlers) {
-                for (Callback callback : callbacks) {
-                    handler.handle(callback, authentication);
-                }
-            }
-        }
-    }
 }

+ 102 - 0
core/src/main/java/org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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.springframework.security.authentication.jaas.memory;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+import org.springframework.util.Assert;
+
+/**
+ * <p>
+ * An in memory representation of a JAAS configuration. The constructor accepts
+ * a Map where the key represents the name of the login context name and the
+ * value is an Array of {@link AppConfigurationEntry} for that login context
+ * name. A default Array of {@link AppConfigurationEntry}s can be specified
+ * which will be returned if a login context is specified which is undefined.
+ * </p>
+ * 
+ * @author Rob Winch
+ */
+public class InMemoryConfiguration extends Configuration {
+    //~ Instance fields ================================================================================================
+
+    private final AppConfigurationEntry[] defaultConfiguration;
+    private final Map<String, AppConfigurationEntry[]> mappedConfigurations;
+
+    //~ Constructors ===================================================================================================
+
+    /**
+     * Creates a new instance with only a defaultConfiguration. Any
+     * configuration name will result in defaultConfiguration being returned.
+     * 
+     * @param defaultConfiguration
+     *            The result for any calls to
+     *            {@link #getAppConfigurationEntry(String)}. Can be
+     *            <code>null</code>.
+     */
+    public InMemoryConfiguration(AppConfigurationEntry[] defaultConfiguration) {
+        this(Collections.<String, AppConfigurationEntry[]> emptyMap(), defaultConfiguration);
+    }
+
+    /**
+     * Creates a new instance with a mapping of login context name to an array
+     * of {@link AppConfigurationEntry}s.
+     * 
+     * @param mappedConfigurations
+     *            each key represents a login context name and each value is an
+     *            Array of {@link AppConfigurationEntry}s that should be used.
+     */
+    public InMemoryConfiguration(Map<String, AppConfigurationEntry[]> mappedConfigurations) {
+        this(mappedConfigurations, null);
+    }
+
+    /**
+     * Creates a new instance with a mapping of login context name to an array
+     * of {@link AppConfigurationEntry}s along with a default configuration that
+     * will be used if no mapping is found for the given login context name.
+     * 
+     * @param mappedConfigurations
+     *            each key represents a login context name and each value is an
+     *            Array of {@link AppConfigurationEntry}s that should be used.
+     * @param defaultConfiguration The result for any calls to
+     *            {@link #getAppConfigurationEntry(String)}. Can be
+     *            <code>null</code>.
+     */
+    public InMemoryConfiguration(Map<String, AppConfigurationEntry[]> mappedConfigurations,
+            AppConfigurationEntry[] defaultConfiguration) {
+        Assert.notNull(mappedConfigurations, "mappedConfigurations cannot be null.");
+        this.mappedConfigurations = mappedConfigurations;
+        this.defaultConfiguration = defaultConfiguration;
+    }
+
+    //~ Methods ========================================================================================================
+
+    @Override
+    public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+        AppConfigurationEntry[] mappedResult = mappedConfigurations.get(name);
+        return mappedResult == null ? defaultConfiguration : mappedResult;
+    }
+
+    /**
+     * Does nothing, but required for JDK5
+     */
+    public void refresh() {
+    }
+}

+ 4 - 0
core/src/main/java/org/springframework/security/authentication/jaas/memory/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * An in memory JAAS implementation.
+ */
+package org.springframework.security.authentication.jaas.memory;

+ 273 - 0
core/src/test/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.java

@@ -0,0 +1,273 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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.springframework.security.authentication.jaas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.logging.Log;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider;
+import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent;
+import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.session.SessionDestroyedEvent;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class DefaultJaasAuthenticationProviderTests {
+    private DefaultJaasAuthenticationProvider provider;
+    private UsernamePasswordAuthenticationToken token;
+    private ApplicationEventPublisher publisher;
+    private Log log;
+
+    @Before
+    public void setUp() throws Exception {
+        Configuration configuration = mock(Configuration.class);
+        publisher = mock(ApplicationEventPublisher.class);
+        log = mock(Log.class);
+        provider = new DefaultJaasAuthenticationProvider();
+        provider.setConfiguration(configuration);
+        provider.setApplicationEventPublisher(publisher);
+        provider.setAuthorityGranters(new AuthorityGranter[] { new TestAuthorityGranter() });
+        provider.afterPropertiesSet();
+        AppConfigurationEntry[] aces = new AppConfigurationEntry[] { new AppConfigurationEntry(
+                TestLoginModule.class.getName(), LoginModuleControlFlag.REQUIRED,
+                Collections.<String, Object> emptyMap()) };
+        when(configuration.getAppConfigurationEntry(provider.getLoginContextName())).thenReturn(aces);
+        token = new UsernamePasswordAuthenticationToken("user", "password");
+        ReflectionTestUtils.setField(provider, "log", log);
+
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void afterPropertiesSetNullConfiguration() throws Exception {
+        provider.setConfiguration(null);
+        provider.afterPropertiesSet();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void afterPropertiesSetNullAuthorityGranters() throws Exception {
+        provider.setAuthorityGranters(null);
+        provider.afterPropertiesSet();
+    }
+
+    @Test
+    public void authenticateUnsupportedAuthentication() {
+        assertEquals(null, provider.authenticate(new TestingAuthenticationToken("user", "password")));
+    }
+
+    @Test
+    public void authenticateSuccess() throws Exception {
+        Authentication auth = provider.authenticate(token);
+        assertEquals(token.getPrincipal(), auth.getPrincipal());
+        assertEquals(token.getCredentials(), auth.getCredentials());
+        assertEquals(true, auth.isAuthenticated());
+        assertEquals(false, auth.getAuthorities().isEmpty());
+        verify(publisher).publishEvent(isA(JaasAuthenticationSuccessEvent.class));
+        verifyNoMoreInteractions(publisher);
+    }
+
+    @Test
+    public void authenticateBadPassword() {
+        try {
+            provider.authenticate(new UsernamePasswordAuthenticationToken("user", "asdf"));
+            fail("LoginException should have been thrown for the bad password");
+        } catch (AuthenticationException success) {
+        }
+
+        verifyFailedLogin();
+    }
+
+    @Test
+    public void authenticateBadUser() {
+        try {
+            provider.authenticate(new UsernamePasswordAuthenticationToken("asdf", "password"));
+            fail("LoginException should have been thrown for the bad user");
+        } catch (AuthenticationException success) {
+        }
+
+        verifyFailedLogin();
+    }
+
+    @Test
+    public void logout() throws Exception {
+        SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
+        SecurityContext securityContext = mock(SecurityContext.class);
+        JaasAuthenticationToken token = mock(JaasAuthenticationToken.class);
+        LoginContext context = mock(LoginContext.class);
+
+        when(event.getSecurityContext()).thenReturn(securityContext);
+        when(securityContext.getAuthentication()).thenReturn(token);
+        when(token.getLoginContext()).thenReturn(context);
+
+        provider.onApplicationEvent(event);
+
+        verify(event).getSecurityContext();
+        verify(securityContext).getAuthentication();
+        verify(token).getLoginContext();
+        verify(context).logout();
+        verifyNoMoreInteractions(event, securityContext, token, context);
+    }
+
+    @Test
+    public void logoutNullSession() {
+        SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
+
+        provider.handleLogout(event);
+
+        verify(event).getSecurityContext();
+        verify(log).debug(anyString());
+        verifyNoMoreInteractions(event);
+    }
+
+    @Test
+    public void logoutNullAuthentication() {
+        SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
+        SecurityContext securityContext = mock(SecurityContext.class);
+
+        when(event.getSecurityContext()).thenReturn(securityContext);
+
+        provider.handleLogout(event);
+
+        verify(event).getSecurityContext();
+        verify(event).getSecurityContext();
+        verify(securityContext).getAuthentication();
+        verifyNoMoreInteractions(event, securityContext);
+    }
+
+    @Test
+    public void logoutNonJaasAuthentication() {
+        SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
+        SecurityContext securityContext = mock(SecurityContext.class);
+
+        when(event.getSecurityContext()).thenReturn(securityContext);
+        when(securityContext.getAuthentication()).thenReturn(token);
+
+        provider.handleLogout(event);
+
+        verify(event).getSecurityContext();
+        verify(event).getSecurityContext();
+        verify(securityContext).getAuthentication();
+        verifyNoMoreInteractions(event, securityContext);
+    }
+
+    @Test
+    public void logoutNullLoginContext() throws Exception {
+        SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
+        SecurityContext securityContext = mock(SecurityContext.class);
+        JaasAuthenticationToken token = mock(JaasAuthenticationToken.class);
+
+        when(event.getSecurityContext()).thenReturn(securityContext);
+        when(securityContext.getAuthentication()).thenReturn(token);
+
+        provider.onApplicationEvent(event);
+        verify(event).getSecurityContext();
+        verify(securityContext).getAuthentication();
+        verify(token).getLoginContext();
+
+        verifyNoMoreInteractions(event, securityContext, token);
+    }
+
+    @Test
+    public void logoutLoginException() throws Exception {
+        SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
+        SecurityContext securityContext = mock(SecurityContext.class);
+        JaasAuthenticationToken token = mock(JaasAuthenticationToken.class);
+        LoginContext context = mock(LoginContext.class);
+        LoginException loginException = new LoginException("Failed Login");
+
+        when(event.getSecurityContext()).thenReturn(securityContext);
+        when(securityContext.getAuthentication()).thenReturn(token);
+        when(token.getLoginContext()).thenReturn(context);
+        doThrow(loginException).when(context).logout();
+
+        provider.onApplicationEvent(event);
+
+        verify(event).getSecurityContext();
+        verify(securityContext).getAuthentication();
+        verify(token).getLoginContext();
+        verify(context).logout();
+        verify(log).warn(anyString(), eq(loginException));
+        verifyNoMoreInteractions(event, securityContext, token, context);
+    }
+
+    @Test
+    public void publishNullPublisher() {
+        provider.setApplicationEventPublisher(null);
+        AuthenticationException ae = new BadCredentialsException("Failed to login", token);
+
+        provider.publishFailureEvent(token, ae);
+        provider.publishSuccessEvent(token);
+    }
+
+    @Test
+    public void javadocExample() {
+        String resName = "/" + getClass().getName().replace('.', '/') + ".xml";
+        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(resName);
+        context.registerShutdownHook();
+        try {
+            provider = context.getBean(DefaultJaasAuthenticationProvider.class);
+            Authentication auth = provider.authenticate(token);
+            assertEquals(true, auth.isAuthenticated());
+            assertEquals(token.getPrincipal(), auth.getPrincipal());
+        } finally {
+            context.close();
+        }
+    }
+
+    private void verifyFailedLogin() {
+        verify(publisher).publishEvent(argThat(new BaseMatcher<JaasAuthenticationFailedEvent>() {
+            public void describeTo(Description desc) {
+                desc.appendText("isA(org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent)");
+                desc.appendText(" && event.getException() != null");
+            }
+
+            public boolean matches(Object arg) {
+                JaasAuthenticationFailedEvent e = (JaasAuthenticationFailedEvent) arg;
+                return e.getException() != null;
+            }
+
+        }));
+        verifyNoMoreInteractions(publisher);
+    }
+}

+ 98 - 0
core/src/test/java/org/springframework/security/authentication/jaas/memory/InMemoryConfigurationTests.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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.springframework.security.authentication.jaas.memory;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.security.authentication.jaas.TestLoginModule;
+
+/**
+ * Tests {@link InMemoryConfiguration}.
+ * 
+ * @author Rob Winch
+ */
+public class InMemoryConfigurationTests {
+
+    private AppConfigurationEntry[] defaultEntries;
+    private Map<String, AppConfigurationEntry[]> mappedEntries;
+
+    @Before
+    public void setUp() {
+        defaultEntries = new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
+                LoginModuleControlFlag.REQUIRED, Collections.<String, Object> emptyMap()) };
+
+        mappedEntries = Collections.<String, AppConfigurationEntry[]> singletonMap("name",
+                new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
+                        LoginModuleControlFlag.OPTIONAL, Collections.<String, Object> emptyMap()) });
+    }
+
+    @Test
+    public void constructorNullDefault() {
+        assertNull(new InMemoryConfiguration((AppConfigurationEntry[]) null).getAppConfigurationEntry("name"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void constructorNullMapped() {
+        new InMemoryConfiguration((Map<String, AppConfigurationEntry[]>) null);
+    }
+
+    @Test
+    public void constructorEmptyMap() {
+        assertNull(new InMemoryConfiguration(Collections.<String, AppConfigurationEntry[]> emptyMap())
+        .getAppConfigurationEntry("name"));
+    }
+
+    @Test
+    public void constructorEmptyMapNullDefault() {
+        assertNull(new InMemoryConfiguration(Collections.<String, AppConfigurationEntry[]> emptyMap(), null)
+        .getAppConfigurationEntry("name"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void constructorNullMapNullDefault() {
+        new InMemoryConfiguration(null, null);
+    }
+
+    @Test
+    public void nonnullDefault() {
+        InMemoryConfiguration configuration = new InMemoryConfiguration(defaultEntries);
+        assertArrayEquals(defaultEntries, configuration.getAppConfigurationEntry("name"));
+    }
+
+    @Test
+    public void mappedNonnullDefault() {
+        InMemoryConfiguration configuration = new InMemoryConfiguration(mappedEntries, defaultEntries);
+        assertArrayEquals(defaultEntries, configuration.getAppConfigurationEntry("missing"));
+        assertArrayEquals(mappedEntries.get("name"), configuration.getAppConfigurationEntry("name"));
+    }
+
+    @Test
+    public void jdk5Compatable() throws Exception {
+        Method method = InMemoryConfiguration.class.getDeclaredMethod("refresh");
+        assertEquals(InMemoryConfiguration.class, method.getDeclaringClass());
+    }
+}

+ 41 - 0
core/src/test/resources/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
+
+    <bean id="jaasAuthProvider"
+        class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
+        <property name="configuration">
+            <bean
+                class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
+                <constructor-arg>
+                    <map>
+                        <entry key="SPRINGSECURITY">
+                            <array>
+                                <bean
+                                    class="javax.security.auth.login.AppConfigurationEntry">
+                                    <constructor-arg
+                                        value="org.springframework.security.authentication.jaas.TestLoginModule" />
+                                    <constructor-arg>
+                                        <util:constant
+                                            static-field="javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED" />
+                                    </constructor-arg>
+                                    <constructor-arg>
+                                        <map></map>
+                                    </constructor-arg>
+                                </bean>
+                            </array>
+                        </entry>
+                    </map>
+                </constructor-arg>
+            </bean>
+        </property>
+        <property name="authorityGranters">
+            <list>
+                <bean
+                    class="org.springframework.security.authentication.jaas.TestAuthorityGranter" />
+            </list>
+        </property>
+    </bean>
+</beans>

+ 136 - 52
docs/manual/src/docbook/jaas-auth-provider.xml

@@ -1,60 +1,26 @@
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="jaas">
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="jaas">
     <info>
         <title>Java Authentication and Authorization Service (JAAS) Provider</title>
     </info>
 
-    <section xml:id="jaas-overview">
+    <section xml:aid="jaas-overview">
         <info>
             <title>Overview</title>
         </info>
         <para>Spring Security provides a package able to delegate authentication requests to the
             Java Authentication and Authorization Service (JAAS). This package is discussed in
             detail below.</para>
-
-        <para>Central to JAAS operation are login configuration files. To learn more about JAAS
-            login configuration files, consult the JAAS reference documentation available from Sun
-            Microsystems. We expect you to have a basic understanding of JAAS and its login
-            configuration file syntax in order to understand this section.</para>
     </section>
-
-    <section xml:id="jaas-config">
-        <info>
-            <title>Configuration</title>
-        </info>
-        <para>The <literal>JaasAuthenticationProvider</literal> attempts to authenticate a user’s
-            principal and credentials through JAAS.</para>
-
-        <para>Let’s assume we have a JAAS login configuration file,
-            <literal>/WEB-INF/login.conf</literal>, with the following contents:
-            <programlisting>
-JAASTest {
-    sample.SampleLoginModule required;
-};</programlisting></para>
-        <para>Like all Spring Security beans, the <classname>JaasAuthenticationProvider</classname>
-            is configured via the application context. The following definitions would correspond to
-            the above JAAS login configuration file: <programlisting><![CDATA[
-<bean id="jaasAuthenticationProvider"
-   class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
- <property name="loginConfig" value="/WEB-INF/login.conf"/>
- <property name="loginContextName" value="JAASTest"/>
- <property name="callbackHandlers">
-  <list>
-   <bean
-     class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>
-   <bean
-     class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>
-  </list>
-  </property>
-  <property name="authorityGranters">
-    <list>
-      <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
-    </list>
-  </property>
-</bean>
-]]></programlisting></para>
-
-        <para>The <literal>CallbackHandler</literal>s and
-            <interfacename>AuthorityGranter</interfacename>s are discussed below.</para>
+    
+    <section xml:id="jaas-abstractjaasauthenticationprovider">
+    	<info>
+    		<title>AbstractJaasAuthenticationProvider</title>
+    	</info>
+    	<para>The <classname>AbstractJaasAuthenticationProvider</classname> is the basis for the 
+    	    provided JAAS <interfacename>AuthenticationProvider</interfacename> implementations. Subclasses 
+    	    must implement a method that creates the <classname>LoginContext</classname>. The 
+    	    <classname>AbstractJaasAuthenticationProvider</classname> has a number of dependencies that can 
+            be injected into it that are discussed below.</para>
 
         <section xml:id="jaas-callbackhandler">
             <info>
@@ -79,7 +45,7 @@ JAASTest {
                 mechanics.</para>
 
             <para>For those needing full control over the callback behavior, internally
-                <literal>JaasAuthenticationProvider</literal> wraps these
+                <classname>AbstractJaasAuthenticationProvider</classname> wraps these
                 <literal>JaasAuthenticationCallbackHandler</literal>s with an
                 <literal>InternalCallbackHandler</literal>. The
                 <literal>InternalCallbackHandler</literal> is the class that actually implements
@@ -107,19 +73,19 @@ JAASTest {
             <para>An <literal>AuthorityGranter</literal> is responsible for inspecting a JAAS
                 principal and returning a set of <literal>String</literal>s, representing the
                 authorities assigned to the principal. For each returned authority string, the
-                <classname>JaasAuthenticationProvider</classname> creates a
+                <classname>AbstractJaasAuthenticationProvider</classname> creates a
                 <classname>JaasGrantedAuthority</classname> (which implements Spring Security’s
                 <interfacename>GrantedAuthority</interfacename> interface) containing the authority
                 string and the JAAS principal that the
                 <interfacename>AuthorityGranter</interfacename> was passed. The
-                <classname>JaasAuthenticationProvider</classname> obtains the JAAS principals by
+                <classname>AbstractJaasAuthenticationProvider</classname> obtains the JAAS principals by
                 firstly successfully authenticating the user’s credentials using the JAAS
                 <literal>LoginModule</literal>, and then accessing the
                 <literal>LoginContext</literal> it returns. A call to
                 <literal>LoginContext.getSubject().getPrincipals()</literal> is made, with each
                 resulting principal passed to each <interfacename>AuthorityGranter</interfacename>
                 defined against the
-                <literal>JaasAuthenticationProvider.setAuthorityGranters(List)</literal>
+                <literal>AbstractJaasAuthenticationProvider.setAuthorityGranters(List)</literal>
                 property.</para>
 
             <para>Spring Security does not include any production
@@ -127,6 +93,124 @@ JAASTest {
                 an implementation-specific meaning. However, there is a
                 <literal>TestAuthorityGranter</literal> in the unit tests that demonstrates a simple
                 <literal>AuthorityGranter</literal> implementation.</para>
-        </section>
+        </section>        
+    </section>
+    <section xml:id="jaas-defaultjaasauthenticationprovider">
+    	<info>
+    		<title>DefaultJaasAuthenticationProvider</title>
+    	</info>
+    	<para>The <classname>DefaultJaasAuthenticationProvider</classname> allows a JAAS 
+            <classname>Configuration</classname> object to be injected into it as a dependency. It then 
+            creates a <classname>LoginContext</classname> using the injected JAAS <classname>Configuration</classname>. 
+            This means that <classname>DefaultJaasAuthenticationProvider</classname> is not bound any particular implementation
+            of <classname>Configuration</classname> as <classname>JaasAuthenticationProvider</classname> is.</para>
+            
+            <section xml:id="jaas-inmemoryconfiguration">
+            	<info>
+            		<title>InMemoryConfiguration</title>
+            	</info>
+            	<para>In order to make it easy to inject a <classname>Configuration</classname> into 
+            	    <classname>DefaultJaasAuthenticationProvider</classname>, a default in memory 
+            	    implementation named <classname>InMemoryConfiguration</classname> is provided. The 
+            	    implementation constructor accepts a <interfacename>Map</interfacename> where each key represents a 
+            	    login configuration name and the value represents an <classname>Array</classname> of 
+            	    <classname>AppConfigurationEntry</classname>s. 
+            	    <classname>InMemoryConfiguration</classname> also supports a default 
+            	    <classname>Array</classname> of <classname>AppConfigurationEntry</classname> objects that 
+            	    will be used if no mapping is found within the provided <interfacename>Map</interfacename>. For 
+            	    details, refer to the class level javadoc of <classname>InMemoryConfiguration</classname>.</para>
+            </section>
+            
+            <section xml:id="jaas-djap-config">
+            	<info>
+            		<title>DefaultJaasAuthenticationProvider Example Configuration</title>
+            	</info>
+            	<para>While the Spring configuration for <classname>InMemoryConfiguration</classname> can be 
+            	    more verbose than the standarad JAAS configuration files, using it in conjuction with 
+            	    <classname>DefaultJaasAuthenticationProvider</classname> is more flexible than 
+            	    <classname>JaasAuthenticationProvider</classname> since it not dependant on the default 
+            	    <classname>Configuration</classname> implementation.</para>
+            	<para>An example configuration of <classname>DefaultJaasAuthenticationProvider</classname> using 
+            	    <classname>InMemoryConfiguration</classname> is provided below. Note that custom implementations of
+            	    <classname>Configuration</classname> can easily be injected into 
+            	    <classname>DefaultJaasAuthenticationProvider</classname> as well.</para>
+            	<programlisting><![CDATA[
+<bean id="jaasAuthProvider" 
+   class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
+ <property name="configuration">
+  <bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
+   <constructor-arg>
+    <map>
+     <!-- 
+       SPRINGSECURITY is the default loginContextName
+       for AbstractJaasAuthenticationProvider
+     -->
+     <entry key="SPRINGSECURITY">
+      <array>
+       <bean class="javax.security.auth.login.AppConfigurationEntry">
+        <constructor-arg value="sample.SampleLoginModule" />
+         <constructor-arg>
+          <util:constant static-field=
+            "javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/>
+         </constructor-arg>
+         <constructor-arg>
+          <map></map>
+         </constructor-arg>
+        </bean>
+       </array>
+      </entry>
+     </map>
+    </constructor-arg>
+   </bean>
+  </property>
+  <property name="authorityGranters">
+   <list>
+    <!-- You will need to write your own implementation of AuthorityGranter -->
+    <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
+   </list>
+  </property>
+</bean>
+]]></programlisting>
+            </section>
+    </section>
+
+    <section xml:id="jaas-jaasauthenticationprovider">
+        <info>
+            <title>JaasAuthenticationProvider</title>
+        </info>
+        <para>The <classname>JaasAuthenticationProvider</classname> assumes the default <classname>Configuration</classname> is an instance of 
+            <link xlink:href="http://download.oracle.com/javase/1.4.2/docs/guide/security/jaas/spec/com/sun/security/auth/login/ConfigFile.html">
+            ConfigFile</link>. This assumption is made in order to attempt to update the <classname>Configuration</classname>. The 
+            <classname>JaasAuthenticationProvider</classname> then uses the default <classname>Configuration</classname> to create the 
+            <classname>LoginContext</classname>.</para>
+
+        <para>Let’s assume we have a JAAS login configuration file,
+            <literal>/WEB-INF/login.conf</literal>, with the following contents:
+            <programlisting>
+JAASTest {
+    sample.SampleLoginModule required;
+};</programlisting></para>
+        <para>Like all Spring Security beans, the <classname>JaasAuthenticationProvider</classname>
+            is configured via the application context. The following definitions would correspond to
+            the above JAAS login configuration file: <programlisting><![CDATA[
+<bean id="jaasAuthenticationProvider"
+   class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
+ <property name="loginConfig" value="/WEB-INF/login.conf"/>
+ <property name="loginContextName" value="JAASTest"/>
+ <property name="callbackHandlers">
+  <list>
+   <bean
+     class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>
+   <bean
+     class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>
+  </list>
+  </property>
+  <property name="authorityGranters">
+    <list>
+      <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
+    </list>
+  </property>
+</bean>
+]]></programlisting></para>
     </section>
-</chapter>
+</chapter>

+ 6 - 0
docs/manual/src/docbook/samples.xml

@@ -129,6 +129,12 @@ Success! Your web filters appear to be properly configured!
             download the CAS Server web application (a war file) from the CAS site and drop it into
             the <filename>samples/cas/server</filename> directory. </para>
     </section>
+    <section xml:id="jaas-sample">
+        <title>JAAS Sample</title>
+        <para>The JAAS sample is very simple example of how to use a JAAS LoginModule with Spring Security. The provided LoginModule will
+            successfully authenticate a user if the username equals the password otherwise a LoginException is thrown. The AuthorityGranter
+            used in this example always grants the role ROLE_USER.</para>
+    </section>
     <section xml:id="preauth-sample">
         <title>Pre-Authentication Sample</title>
         <para> This sample application demonstrates how to wire up beans from the <link

+ 25 - 0
samples/jaas/jaas.gradle

@@ -0,0 +1,25 @@
+// JAAS sample build file
+
+apply plugin: 'war'
+apply plugin: 'jetty'
+
+dependencies {
+    providedCompile 'javax.servlet:servlet-api:2.5@jar'
+
+    compile project(':spring-security-core'),
+            "org.springframework:spring-beans:$springVersion",
+            "org.springframework:spring-context:$springVersion",
+            "org.springframework:spring-web:$springVersion"
+
+    runtime project(':spring-security-web'),
+            project(':spring-security-config'),
+            project(':spring-security-taglibs'),
+            "org.springframework:spring-context-support:$springVersion",
+            "javax.servlet:jstl:$jstlVersion",
+            "org.slf4j:jcl-over-slf4j:$slf4jVersion",
+            "ch.qos.logback:logback-classic:$logbackVersion"
+}
+
+jettyRun {
+    contextPath = "/jaas"
+}

+ 34 - 0
samples/jaas/src/main/java/samples/jaas/RoleUserAuthorityGranter.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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 samples.jaas;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.security.authentication.jaas.AuthorityGranter;
+
+/**
+ * An AuthorityGranter that always grants "ROLE_USER".
+ * 
+ * @author Rob Winch
+ */
+public class RoleUserAuthorityGranter implements AuthorityGranter {
+
+    public Set<String> grant(Principal principal) {
+        return Collections.singleton("ROLE_USER");
+    }
+}

+ 85 - 0
samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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 samples.jaas;
+
+import java.security.Principal;
+import java.util.Map;
+
+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.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+/**
+ * A LoginModule that will allow login if the username equals the password. Upon
+ * successful authentication it adds the username as a Principal.
+ *
+ * @author Rob Winch
+ */
+public class UsernameEqualsPasswordLoginModule implements LoginModule {
+    //~ Instance fields ================================================================================================
+
+    private String password;
+    private String username;
+    private Subject subject;
+
+    //~ Methods ========================================================================================================
+
+    public boolean abort() throws LoginException {
+        return true;
+    }
+
+    public boolean commit() throws LoginException {
+        return true;
+    }
+
+    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+            Map<String, ?> options) {
+        this.subject = subject;
+
+        try {
+            NameCallback nameCallback = new NameCallback("prompt");
+            PasswordCallback passwordCallback = new PasswordCallback("prompt", false);
+
+            callbackHandler.handle(new Callback[] { nameCallback, passwordCallback });
+
+            password = new String(passwordCallback.getPassword());
+            username = nameCallback.getName();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean login() throws LoginException {
+        if (username != null && !username.equals(password)) {
+            throw new LoginException("username is not equal to password");
+        }
+
+        subject.getPrincipals().add(new Principal() {
+            public String getName() {
+                return username;
+            }
+        });
+        return true;
+    }
+
+    public boolean logout() throws LoginException {
+        return true;
+    }
+}

+ 52 - 0
samples/jaas/src/main/resources/applicationContext-security.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:sec="http://www.springframework.org/schema/security"
+	xmlns:p="http://www.springframework.org/schema/p"
+	xmlns:util="http://www.springframework.org/schema/util"
+	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
+		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
+
+
+	<sec:http auto-config="true" use-expressions="true">
+		<sec:intercept-url pattern="/secure/**" access="isAuthenticated()"/>
+	</sec:http>
+	
+	<sec:authentication-manager>
+		<sec:authentication-provider ref="jaasAuthProvider"/>
+	</sec:authentication-manager>
+
+	<bean id="jaasAuthProvider"
+	class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
+		<property name="configuration">
+			<bean
+				class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
+				<constructor-arg>
+					<map>
+						<entry key="SPRINGSECURITY">
+							<array>
+								<bean class="javax.security.auth.login.AppConfigurationEntry">
+									<constructor-arg
+										value="samples.jaas.UsernameEqualsPasswordLoginModule" />
+									<constructor-arg>
+										<util:constant
+											static-field="javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED" />
+									</constructor-arg>
+									<constructor-arg>
+										<map></map>
+									</constructor-arg>
+								</bean>
+							</array>
+						</entry>
+					</map>
+				</constructor-arg>
+			</bean>
+		</property>
+		<property name="authorityGranters">
+			<list>
+				<bean class="samples.jaas.RoleUserAuthorityGranter" />
+			</list>
+		</property>
+	</bean>
+</beans>

+ 63 - 0
samples/jaas/src/main/webapp/WEB-INF/web.xml

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  - JAAS web application
+  -
+  -->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+    <display-name>JAAS Sample Application</display-name>
+
+    <!--
+      - Location of the XML file that defines the root application context
+      - Applied by ContextLoaderListener.
+      -->
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>
+           classpath:applicationContext-security.xml
+        </param-value>
+    </context-param>
+
+   <!-- Nothing below here needs to be modified -->
+
+    <context-param>
+        <param-name>webAppRootKey</param-name>
+        <param-value>jaas.root</param-value>
+    </context-param>
+
+    <filter>
+        <filter-name>localizationFilter</filter-name>
+        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
+    </filter>
+
+    <filter>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>localizationFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter-mapping>
+      <filter-name>springSecurityFilterChain</filter-name>
+      <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <!--
+      - Loads the root application context of this web app at startup.
+      - The application context is then available via
+      - WebApplicationContextUtils.getWebApplicationContext(servletContext).
+    -->
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+
+     <welcome-file-list>
+        <welcome-file>index.jsp</welcome-file>
+    </welcome-file-list>
+</web-app>

+ 18 - 0
samples/jaas/src/main/webapp/index.jsp

@@ -0,0 +1,18 @@
+<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
+<html>
+<body>
+<h1>Home Page</h1>
+<p>
+Anyone can view this page.
+</p>
+<p>
+Your principal object is....: <%= request.getUserPrincipal() %>
+</p>
+<p>
+<sec:authorize url='/secure/index.jsp'>You can currently access "/secure" URLs.</sec:authorize>
+</p>
+
+<p>
+<a href="secure/index.jsp">Secure page</a></p>
+</body>
+</html>

+ 40 - 0
samples/jaas/src/main/webapp/secure/index.jsp

@@ -0,0 +1,40 @@
+<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
+<%@ page import="org.springframework.security.core.Authentication" %>
+<%@ page import="org.springframework.security.core.GrantedAuthority" %>
+
+<html>
+<head>
+<title>Security Debug Information</title>
+</head>
+<body>
+
+<h3>Security Debug Information</h3>
+
+<%
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        if (auth != null) { %>
+<p>
+            Authentication object is of type: <em><%= auth.getClass().getName() %></em>
+</p>
+<p>
+            Authentication object as a String: <br/><br/><%= auth.toString() %>
+</p>
+
+            Authentication object holds the following granted authorities:<br /><br />
+<%
+            for (GrantedAuthority authority : auth.getAuthorities()) { %>
+                <%= authority %> (<em>getAuthority()</em>: <%= authority.getAuthority() %>)<br />
+<%			}
+%>
+
+                <p><b>Success! Your web filters appear to be properly configured!</b></p>
+<%
+        } else {
+%>
+            Authentication object is null.<br />
+            This is an error and your Spring Security application will not operate properly until corrected.<br /><br />
+<%		}
+%>
+
+</body>
+</html>

+ 2 - 1
settings.gradle

@@ -19,7 +19,8 @@ def String[] samples = [
     'dms',
     'preauth',
     'cas',
-    'ldap'
+    'ldap',
+    'jaas'
 ]
 
 def String[] itest = [