From 58d9903ebcdfa738ecdfefa098df9a965fc4392d Mon Sep 17 00:00:00 2001
From: rwinch
Date: Fri, 10 Sep 2010 20:17:22 -0500
Subject: [PATCH] SEC-1564: JAAS Configuration can now be injected into
DefaultJaasAuthenticationProvider
---
core/core.gradle | 3 +-
.../AbstractJaasAuthenticationProvider.java | 365 ++++++++++++++++++
.../DefaultJaasAuthenticationProvider.java | 123 ++++++
.../jaas/JaasAuthenticationProvider.java | 260 +------------
.../jaas/memory/InMemoryConfiguration.java | 102 +++++
.../jaas/memory/package-info.java | 4 +
...efaultJaasAuthenticationProviderTests.java | 273 +++++++++++++
.../memory/InMemoryConfigurationTests.java | 98 +++++
...DefaultJaasAuthenticationProviderTests.xml | 41 ++
.../manual/src/docbook/jaas-auth-provider.xml | 258 ++++++++-----
docs/manual/src/docbook/samples.xml | 6 +
samples/jaas/jaas.gradle | 25 ++
.../jaas/RoleUserAuthorityGranter.java | 34 ++
.../UsernameEqualsPasswordLoginModule.java | 85 ++++
.../resources/applicationContext-security.xml | 52 +++
samples/jaas/src/main/webapp/WEB-INF/web.xml | 63 +++
samples/jaas/src/main/webapp/index.jsp | 18 +
samples/jaas/src/main/webapp/secure/index.jsp | 40 ++
settings.gradle | 3 +-
19 files changed, 1521 insertions(+), 332 deletions(-)
create mode 100644 core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java
create mode 100644 core/src/main/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProvider.java
create mode 100644 core/src/main/java/org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.java
create mode 100644 core/src/main/java/org/springframework/security/authentication/jaas/memory/package-info.java
create mode 100644 core/src/test/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.java
create mode 100644 core/src/test/java/org/springframework/security/authentication/jaas/memory/InMemoryConfigurationTests.java
create mode 100644 core/src/test/resources/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.xml
create mode 100644 samples/jaas/jaas.gradle
create mode 100644 samples/jaas/src/main/java/samples/jaas/RoleUserAuthorityGranter.java
create mode 100644 samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java
create mode 100644 samples/jaas/src/main/resources/applicationContext-security.xml
create mode 100644 samples/jaas/src/main/webapp/WEB-INF/web.xml
create mode 100644 samples/jaas/src/main/webapp/index.jsp
create mode 100644 samples/jaas/src/main/webapp/secure/index.jsp
diff --git a/core/core.gradle b/core/core.gradle
index 4b3bd7db78..b9aa212458 100644
--- a/core/core.gradle
+++ b/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"
}
diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java
new file mode 100644
index 0000000000..01a63a20b9
--- /dev/null
+++ b/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.
+ *
+ *
This AuthenticationProvider is capable of validating {@link
+ * org.springframework.security.authentication.UsernamePasswordAuthenticationToken} requests contain the correct username and
+ * password.
+ *
This implementation is backed by a JAAS configuration that is provided by
+ * a subclass's implementation of {@link #createLoginContext(CallbackHandler)}.
+ *
+ *
When using JAAS login modules as the authentication source, sometimes the
+ * LoginContext will
+ * require CallbackHandlers. The AbstractJaasAuthenticationProvider uses an internal
+ * CallbackHandler
+ * 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.
+ *
+ *
{@link JaasAuthenticationCallbackHandler}s are passed to the AbstractJaasAuthenticationProvider through the {@link
+ * #setCallbackHandlers(org.springframework.security.authentication.jaas.JaasAuthenticationCallbackHandler[]) callbackHandlers}
+ * property.
+ *
+ * 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}.
+ *
AuthorityGranters are configured in spring xml as follows...
+ *
+ *
+ * @author Ray Krueger
+ * @author Rob Winch
+ */
+public abstract class AbstractJaasAuthenticationProvider implements AuthenticationProvider,
+ApplicationEventPublisherAware, InitializingBean, ApplicationListener {
+ //~ 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 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();
+ authorities.addAll(request.getAuthorities());
+
+ // Get the subject principals and pass them to each of the AuthorityGranters
+ Set principals = loginContext.getSubject().getPrincipals();
+
+ for (Principal principal : principals) {
+ for (AuthorityGranter granter : authorityGranters) {
+ Set 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(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 null).
+ * @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. MUST NOT use
+ * SecurityContextHolder as we are logging out a session that is not related to the current user.
+ *
+ * @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);
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProvider.java
new file mode 100644
index 0000000000..e0f6d45013
--- /dev/null
+++ b/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;
+
+/**
+ *
+ * Creates a LoginContext using the Configuration provided to it. This allows
+ * the configuration to be injected regardless of the value of
+ * {@link Configuration#getConfiguration()}.
+ *
+ *
+ * While not bound to any particular Configuration implementation, an in memory version of a JAAS
+ * configuration can be represented using {@link InMemoryConfiguration}.
+ *
+ *
+ * @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;
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java
index 1323d6dbd0..88a11dee23 100644
--- a/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java
+++ b/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;
*
*
* @author Ray Krueger
+ * @author Rob Winch
*/
-public class JaasAuthenticationProvider implements AuthenticationProvider, ApplicationEventPublisherAware,
- InitializingBean, ApplicationListener {
+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 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();
- authorities.addAll(request.getAuthorities());
-
- // Get the subject principals and pass them to each of the AuthorityGranters
- Set principals = loginContext.getSubject().getPrincipals();
-
- for (Principal principal : principals) {
- for (AuthorityGranter granter : authorityGranters) {
- Set 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(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. MUST NOT use
- * SecurityContextHolder as we are logging out a session that is not related to the current user.
- *
- * @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);
- }
- }
- }
- }
}
diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.java b/core/src/main/java/org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.java
new file mode 100644
index 0000000000..2746364e18
--- /dev/null
+++ b/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;
+
+/**
+ *
+ * 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.
+ *
+ *
+ * @author Rob Winch
+ */
+public class InMemoryConfiguration extends Configuration {
+ //~ Instance fields ================================================================================================
+
+ private final AppConfigurationEntry[] defaultConfiguration;
+ private final Map 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
+ * null.
+ */
+ public InMemoryConfiguration(AppConfigurationEntry[] defaultConfiguration) {
+ this(Collections. 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 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
+ * null.
+ */
+ public InMemoryConfiguration(Map 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() {
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/memory/package-info.java b/core/src/main/java/org/springframework/security/authentication/jaas/memory/package-info.java
new file mode 100644
index 0000000000..fd08f58c5a
--- /dev/null
+++ b/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;
diff --git a/core/src/test/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.java
new file mode 100644
index 0000000000..3effeb7621
--- /dev/null
+++ b/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. 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() {
+ 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);
+ }
+}
diff --git a/core/src/test/java/org/springframework/security/authentication/jaas/memory/InMemoryConfigurationTests.java b/core/src/test/java/org/springframework/security/authentication/jaas/memory/InMemoryConfigurationTests.java
new file mode 100644
index 0000000000..02688d7eb1
--- /dev/null
+++ b/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 mappedEntries;
+
+ @Before
+ public void setUp() {
+ defaultEntries = new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
+ LoginModuleControlFlag.REQUIRED, Collections. emptyMap()) };
+
+ mappedEntries = Collections. singletonMap("name",
+ new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
+ LoginModuleControlFlag.OPTIONAL, Collections. emptyMap()) });
+ }
+
+ @Test
+ public void constructorNullDefault() {
+ assertNull(new InMemoryConfiguration((AppConfigurationEntry[]) null).getAppConfigurationEntry("name"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void constructorNullMapped() {
+ new InMemoryConfiguration((Map) null);
+ }
+
+ @Test
+ public void constructorEmptyMap() {
+ assertNull(new InMemoryConfiguration(Collections. emptyMap())
+ .getAppConfigurationEntry("name"));
+ }
+
+ @Test
+ public void constructorEmptyMapNullDefault() {
+ assertNull(new InMemoryConfiguration(Collections. 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());
+ }
+}
diff --git a/core/src/test/resources/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.xml b/core/src/test/resources/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.xml
new file mode 100644
index 0000000000..c1303be7fb
--- /dev/null
+++ b/core/src/test/resources/org/springframework/security/authentication/jaas/DefaultJaasAuthenticationProviderTests.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/manual/src/docbook/jaas-auth-provider.xml b/docs/manual/src/docbook/jaas-auth-provider.xml
index 675e5d750a..39f3a01b9e 100644
--- a/docs/manual/src/docbook/jaas-auth-provider.xml
+++ b/docs/manual/src/docbook/jaas-auth-provider.xml
@@ -1,28 +1,188 @@
-
+Java Authentication and Authorization Service (JAAS) Provider
-
+ OverviewSpring Security provides a package able to delegate authentication requests to the
Java Authentication and Authorization Service (JAAS). This package is discussed in
detail below.
+
+
+
+
+ AbstractJaasAuthenticationProvider
+
+ The AbstractJaasAuthenticationProvider is the basis for the
+ provided JAAS AuthenticationProvider implementations. Subclasses
+ must implement a method that creates the LoginContext. The
+ AbstractJaasAuthenticationProvider has a number of dependencies that can
+ be injected into it that are discussed below.
- 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.
+
+
+ JAAS CallbackHandler
+
+
+ Most JAAS LoginModules require a callback of some sort. These
+ callbacks are usually used to obtain the username and password from the user.
+
+ In a Spring Security deployment, Spring Security is responsible for this user
+ interaction (via the authentication mechanism). Thus, by the time the authentication
+ request is delegated through to JAAS, Spring Security's authentication mechanism
+ will already have fully-populated an Authentication
+ object containing all the information required by the JAAS
+ LoginModule.
+
+ Therefore, the JAAS package for Spring Security provides two default callback
+ handlers, JaasNameCallbackHandler and
+ JaasPasswordCallbackHandler. Each of these callback handlers
+ implement JaasAuthenticationCallbackHandler. In most cases these
+ callback handlers can simply be used without understanding the internal
+ mechanics.
+
+ For those needing full control over the callback behavior, internally
+ AbstractJaasAuthenticationProvider wraps these
+ JaasAuthenticationCallbackHandlers with an
+ InternalCallbackHandler. The
+ InternalCallbackHandler is the class that actually implements
+ JAAS’ normal CallbackHandler interface. Any time that the JAAS
+ LoginModule is used, it is passed a list of application context
+ configured InternalCallbackHandlers. If the
+ LoginModule requests a callback against the
+ InternalCallbackHandlers, the callback is in-turn passed to the
+ JaasAuthenticationCallbackHandlers being wrapped.
+
+
+
+
+ JAAS AuthorityGranter
+
+
+ JAAS works with principals. Even "roles" are represented as principals in JAAS.
+ Spring Security, on the other hand, works with
+ Authentication objects. Each
+ Authentication object contains a single principal,
+ and multiple GrantedAuthoritys. To facilitate
+ mapping between these different concepts, Spring Security's JAAS package includes an
+ AuthorityGranter interface.
+
+ An AuthorityGranter is responsible for inspecting a JAAS
+ principal and returning a set of Strings, representing the
+ authorities assigned to the principal. For each returned authority string, the
+ AbstractJaasAuthenticationProvider creates a
+ JaasGrantedAuthority (which implements Spring Security’s
+ GrantedAuthority interface) containing the authority
+ string and the JAAS principal that the
+ AuthorityGranter was passed. The
+ AbstractJaasAuthenticationProvider obtains the JAAS principals by
+ firstly successfully authenticating the user’s credentials using the JAAS
+ LoginModule, and then accessing the
+ LoginContext it returns. A call to
+ LoginContext.getSubject().getPrincipals() is made, with each
+ resulting principal passed to each AuthorityGranter
+ defined against the
+ AbstractJaasAuthenticationProvider.setAuthorityGranters(List)
+ property.
+
+ Spring Security does not include any production
+ AuthorityGranters given that every JAAS principal has
+ an implementation-specific meaning. However, there is a
+ TestAuthorityGranter in the unit tests that demonstrates a simple
+ AuthorityGranter implementation.
+
+
+
+
+ DefaultJaasAuthenticationProvider
+
+ The DefaultJaasAuthenticationProvider allows a JAAS
+ Configuration object to be injected into it as a dependency. It then
+ creates a LoginContext using the injected JAAS Configuration.
+ This means that DefaultJaasAuthenticationProvider is not bound any particular implementation
+ of Configuration as JaasAuthenticationProvider is.
+
+
+
+ InMemoryConfiguration
+
+ In order to make it easy to inject a Configuration into
+ DefaultJaasAuthenticationProvider, a default in memory
+ implementation named InMemoryConfiguration is provided. The
+ implementation constructor accepts a Map where each key represents a
+ login configuration name and the value represents an Array of
+ AppConfigurationEntrys.
+ InMemoryConfiguration also supports a default
+ Array of AppConfigurationEntry objects that
+ will be used if no mapping is found within the provided Map. For
+ details, refer to the class level javadoc of InMemoryConfiguration.
+
+
+
+
+ DefaultJaasAuthenticationProvider Example Configuration
+
+ While the Spring configuration for InMemoryConfiguration can be
+ more verbose than the standarad JAAS configuration files, using it in conjuction with
+ DefaultJaasAuthenticationProvider is more flexible than
+ JaasAuthenticationProvider since it not dependant on the default
+ Configuration implementation.
+ An example configuration of DefaultJaasAuthenticationProvider using
+ InMemoryConfiguration is provided below. Note that custom implementations of
+ Configuration can easily be injected into
+ DefaultJaasAuthenticationProvider as well.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
-
+
- Configuration
+ JaasAuthenticationProvider
- The JaasAuthenticationProvider attempts to authenticate a user’s
- principal and credentials through JAAS.
+ The JaasAuthenticationProvider assumes the default Configuration is an instance of
+
+ ConfigFile. This assumption is made in order to attempt to update the Configuration. The
+ JaasAuthenticationProvider then uses the default Configuration to create the
+ LoginContext.Let’s assume we have a JAAS login configuration file,
/WEB-INF/login.conf, with the following contents:
@@ -52,81 +212,5 @@ JAASTest {
]]>
-
- The CallbackHandlers and
- AuthorityGranters are discussed below.
-
-
-
- JAAS CallbackHandler
-
-
- Most JAAS LoginModules require a callback of some sort. These
- callbacks are usually used to obtain the username and password from the user.
-
- In a Spring Security deployment, Spring Security is responsible for this user
- interaction (via the authentication mechanism). Thus, by the time the authentication
- request is delegated through to JAAS, Spring Security's authentication mechanism
- will already have fully-populated an Authentication
- object containing all the information required by the JAAS
- LoginModule.
-
- Therefore, the JAAS package for Spring Security provides two default callback
- handlers, JaasNameCallbackHandler and
- JaasPasswordCallbackHandler. Each of these callback handlers
- implement JaasAuthenticationCallbackHandler. In most cases these
- callback handlers can simply be used without understanding the internal
- mechanics.
-
- For those needing full control over the callback behavior, internally
- JaasAuthenticationProvider wraps these
- JaasAuthenticationCallbackHandlers with an
- InternalCallbackHandler. The
- InternalCallbackHandler is the class that actually implements
- JAAS’ normal CallbackHandler interface. Any time that the JAAS
- LoginModule is used, it is passed a list of application context
- configured InternalCallbackHandlers. If the
- LoginModule requests a callback against the
- InternalCallbackHandlers, the callback is in-turn passed to the
- JaasAuthenticationCallbackHandlers being wrapped.
-
-
-
-
- JAAS AuthorityGranter
-
-
- JAAS works with principals. Even "roles" are represented as principals in JAAS.
- Spring Security, on the other hand, works with
- Authentication objects. Each
- Authentication object contains a single principal,
- and multiple GrantedAuthoritys. To facilitate
- mapping between these different concepts, Spring Security's JAAS package includes an
- AuthorityGranter interface.
-
- An AuthorityGranter is responsible for inspecting a JAAS
- principal and returning a set of Strings, representing the
- authorities assigned to the principal. For each returned authority string, the
- JaasAuthenticationProvider creates a
- JaasGrantedAuthority (which implements Spring Security’s
- GrantedAuthority interface) containing the authority
- string and the JAAS principal that the
- AuthorityGranter was passed. The
- JaasAuthenticationProvider obtains the JAAS principals by
- firstly successfully authenticating the user’s credentials using the JAAS
- LoginModule, and then accessing the
- LoginContext it returns. A call to
- LoginContext.getSubject().getPrincipals() is made, with each
- resulting principal passed to each AuthorityGranter
- defined against the
- JaasAuthenticationProvider.setAuthorityGranters(List)
- property.
-
- Spring Security does not include any production
- AuthorityGranters given that every JAAS principal has
- an implementation-specific meaning. However, there is a
- TestAuthorityGranter in the unit tests that demonstrates a simple
- AuthorityGranter implementation.
-
-
+
\ No newline at end of file
diff --git a/docs/manual/src/docbook/samples.xml b/docs/manual/src/docbook/samples.xml
index 1530402c0a..0e21e81b22 100644
--- a/docs/manual/src/docbook/samples.xml
+++ b/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 samples/cas/server directory.
+
+ JAAS Sample
+ 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.
+ Pre-Authentication Sample This sample application demonstrates how to wire up beans from the grant(Principal principal) {
+ return Collections.singleton("ROLE_USER");
+ }
+}
diff --git a/samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java b/samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java
new file mode 100644
index 0000000000..213ed42b8b
--- /dev/null
+++ b/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 sharedState,
+ Map 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;
+ }
+}
diff --git a/samples/jaas/src/main/resources/applicationContext-security.xml b/samples/jaas/src/main/resources/applicationContext-security.xml
new file mode 100644
index 0000000000..d658938c7b
--- /dev/null
+++ b/samples/jaas/src/main/resources/applicationContext-security.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/jaas/src/main/webapp/WEB-INF/web.xml b/samples/jaas/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..3af33ec5df
--- /dev/null
+++ b/samples/jaas/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+ JAAS Sample Application
+
+
+
+ contextConfigLocation
+
+ classpath:applicationContext-security.xml
+
+
+
+
+
+
+ webAppRootKey
+ jaas.root
+
+
+
+ localizationFilter
+ org.springframework.web.filter.RequestContextFilter
+
+
+
+ springSecurityFilterChain
+ org.springframework.web.filter.DelegatingFilterProxy
+
+
+
+ localizationFilter
+ /*
+
+
+
+ springSecurityFilterChain
+ /*
+
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+
+ index.jsp
+
+
diff --git a/samples/jaas/src/main/webapp/index.jsp b/samples/jaas/src/main/webapp/index.jsp
new file mode 100644
index 0000000000..f0dfc20ac8
--- /dev/null
+++ b/samples/jaas/src/main/webapp/index.jsp
@@ -0,0 +1,18 @@
+<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
+
+
+
Home Page
+
+Anyone can view this page.
+
+
+Your principal object is....: <%= request.getUserPrincipal() %>
+