From de819378fc096641da05c4d7c29b1c42a8b134ac Mon Sep 17 00:00:00 2001
From: rwinch
Date: Mon, 13 Sep 2010 13:12:45 -0500
Subject: [PATCH] SEC-1536: added JAAS API Integration, updated doc, updated
jaas sample
---
.../http/DefaultFilterChainValidator.java | 2 +
.../config/http/HttpConfigurationBuilder.java | 23 +-
.../security/config/http/SecurityFilters.java | 1 +
.../security/config/spring-security-3.1.rnc | 3 +
.../security/config/spring-security-3.1.xsd | 5 +
.../config/http/MiscHttpConfigTests.groovy | 9 +
.../manual/src/docbook/appendix-namespace.xml | 7 +
.../manual/src/docbook/jaas-auth-provider.xml | 16 ++
docs/manual/src/docbook/namespace-config.xml | 5 +
docs/manual/src/docbook/samples.xml | 3 +-
.../src/docbook/security-filter-chain.xml | 7 +
samples/jaas/jaas.gradle | 1 -
.../UsernameEqualsPasswordLoginModule.java | 5 +-
.../resources/applicationContext-security.xml | 2 +-
samples/jaas/src/main/webapp/index.jsp | 5 +
samples/jaas/src/main/webapp/secure/index.jsp | 11 +
.../jaas/JaasApiIntegrationFilter.java | 163 ++++++++++++++
.../jaas/JaasApiIntegrationFilterTest.java | 211 ++++++++++++++++++
web/web.gradle | 3 +-
19 files changed, 476 insertions(+), 6 deletions(-)
create mode 100644 web/src/main/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilter.java
create mode 100644 web/src/test/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilterTest.java
diff --git a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java
index a1093692fa..02a7be95c4 100644
--- a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java
+++ b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java
@@ -17,6 +17,7 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.jaas.JaasApiIntegrationFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
@@ -52,6 +53,7 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain
checkForDuplicates(SessionManagementFilter.class, filters);
checkForDuplicates(BasicAuthenticationFilter.class, filters);
checkForDuplicates(SecurityContextHolderAwareRequestFilter.class, filters);
+ checkForDuplicates(JaasApiIntegrationFilter.class, filters);
checkForDuplicates(ExceptionTranslationFilter.class, filters);
checkForDuplicates(FilterSecurityInterceptor.class, filters);
}
diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
index f3ac8d6a42..5b2ccfa8da 100644
--- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
+++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
@@ -4,7 +4,6 @@ import static org.springframework.security.config.http.HttpSecurityBeanDefinitio
import static org.springframework.security.config.http.SecurityFilters.*;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -35,6 +34,7 @@ import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.security.web.authentication.jaas.JaasApiIntegrationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
@@ -90,6 +90,7 @@ class HttpConfigurationBuilder {
private BeanReference sessionStrategyRef;
private RootBeanDefinition sfpf;
private BeanDefinition servApiFilter;
+ private BeanDefinition jaasApiFilter;
private final String portMapperName;
private BeanReference fsi;
private BeanReference requestCache;
@@ -123,6 +124,7 @@ class HttpConfigurationBuilder {
createSessionManagementFilters();
createRequestCacheFilter();
createServletApiFilter();
+ createJaasApiFilter();
createChannelProcessingFilter();
createFilterSecurityInterceptor(authenticationManager);
}
@@ -337,7 +339,22 @@ class HttpConfigurationBuilder {
servApiFilter = new RootBeanDefinition(SecurityContextHolderAwareRequestFilter.class);
}
}
+
+ // Adds the jaas-api integration filter if required
+ private void createJaasApiFilter() {
+ final String ATT_JAAS_API_PROVISION = "jaas-api-provision";
+ final String DEF_JAAS_API_PROVISION = "false";
+ String provideJaasApi = httpElt.getAttribute(ATT_JAAS_API_PROVISION);
+ if (!StringUtils.hasText(provideJaasApi)) {
+ provideJaasApi = DEF_JAAS_API_PROVISION;
+ }
+
+ if ("true".equals(provideJaasApi)) {
+ jaasApiFilter = new RootBeanDefinition(JaasApiIntegrationFilter.class);
+ }
+ }
+
private void createChannelProcessingFilter() {
ManagedMap channelRequestMap = parseInterceptUrlsForChannelSecurity();
@@ -514,6 +531,10 @@ class HttpConfigurationBuilder {
filters.add(new OrderDecorator(servApiFilter, SERVLET_API_SUPPORT_FILTER));
}
+ if (jaasApiFilter != null) {
+ filters.add(new OrderDecorator(jaasApiFilter, JAAS_API_SUPPORT_FILTER));
+ }
+
if (sfpf != null) {
filters.add(new OrderDecorator(sfpf, SESSION_MANAGEMENT_FILTER));
}
diff --git a/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java b/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
index af9ea1926c..0e7568bb9d 100644
--- a/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
+++ b/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
@@ -23,6 +23,7 @@ enum SecurityFilters {
BASIC_AUTH_FILTER,
REQUEST_CACHE_FILTER,
SERVLET_API_SUPPORT_FILTER,
+ JAAS_API_SUPPORT_FILTER,
REMEMBER_ME_FILTER,
ANONYMOUS_FILTER,
SESSION_MANAGEMENT_FILTER,
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
index 1ee992a4f2..b138557177 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
@@ -289,6 +289,9 @@ http.attlist &=
http.attlist &=
## Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".
attribute servlet-api-provision {boolean}?
+http.attlist &=
+ ## If available, runs the request as the Subject acquired from the JaasAuthenticationToken. Defaults to "false".
+ attribute jaas-api-provision {boolean}?
http.attlist &=
## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
attribute access-decision-manager-ref {xsd:token}?
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
index a5754eba1a..e950e484f0 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
@@ -759,6 +759,11 @@
Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".
+
+
+ If available, runs the request as the Subject acquired from the JaasAuthenticationToken. Defaults to "false".
+
+
Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
diff --git a/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
index c21e56c1df..919d2d386b 100644
--- a/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
@@ -30,6 +30,7 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.security.web.authentication.jaas.JaasApiIntegrationFilter
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
@@ -568,6 +569,14 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
getFilter(BasicAuthenticationFilter).authenticationDetailsSource == adsr
getFilter(X509AuthenticationFilter).authenticationDetailsSource == adsr
}
+
+ def includeJaasApiIntegrationFilter() {
+ xml.http(['auto-config':'true','jaas-api-provision':'true'])
+ createAppContext()
+ expect:
+ getFilter(JaasApiIntegrationFilter.class) != null
+
+ }
}
class MockEntryPoint extends LoginUrlAuthenticationEntryPoint {
diff --git a/docs/manual/src/docbook/appendix-namespace.xml b/docs/manual/src/docbook/appendix-namespace.xml
index 773caead62..63f3573b8a 100644
--- a/docs/manual/src/docbook/appendix-namespace.xml
+++ b/docs/manual/src/docbook/appendix-namespace.xml
@@ -70,6 +70,13 @@
SecurityContextHolderAwareRequestFilter bean to the
stack. Defaults to "true".
+
+ jaas-api-provision
+ If available, runs the request as the Subject acquired from
+ the JaasAuthenticationToken which is implemented by
+ adding a JaasApiIntegrationFilter bean to the stack.
+ Defaults to "false".
+
request-matcher
Defines the RequestMatcher strategy used in
diff --git a/docs/manual/src/docbook/jaas-auth-provider.xml b/docs/manual/src/docbook/jaas-auth-provider.xml
index 39f3a01b9e..e50652a69e 100644
--- a/docs/manual/src/docbook/jaas-auth-provider.xml
+++ b/docs/manual/src/docbook/jaas-auth-provider.xml
@@ -213,4 +213,20 @@ JAASTest {
]]>
+
+
+ Running as a Subject
+
+ If configured, the JaasApiIntegrationFilter will attempt to
+ run as the Subject on the
+ JaasAuthenticationToken . This means that the
+ Subject can be accessed using:
+
+ This integration can easily be configured using the
+ jaas-api-provision attribute. This
+ feature is useful when integrating with legacy or external API's that rely on the
+ JAAS Subject being populated.
+
\ No newline at end of file
diff --git a/docs/manual/src/docbook/namespace-config.xml b/docs/manual/src/docbook/namespace-config.xml
index 67d5df92ce..51a3baf3bd 100644
--- a/docs/manual/src/docbook/namespace-config.xml
+++ b/docs/manual/src/docbook/namespace-config.xml
@@ -691,6 +691,11 @@ List<OpenIDAttribute> attributes = token.getAttributes();The
SecurityContextHolderAwareFilter
http/@servlet-api-provision
+
+ JAAS_API_SUPPORT_FILTER
+ JaasApiIntegrationFilter
+ http/@jaas-api-provision
+
REMEMBER_ME_FILTER
RememberMeAuthenticationFilter
diff --git a/docs/manual/src/docbook/samples.xml b/docs/manual/src/docbook/samples.xml
index 0e21e81b22..227c6d2590 100644
--- a/docs/manual/src/docbook/samples.xml
+++ b/docs/manual/src/docbook/samples.xml
@@ -133,7 +133,8 @@ Success! Your web filters appear to be properly configured!
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.
+ used in this example always grants the role ROLE_USER. The sample application also demonstrates how to run as the JAAS Subject
+ returned by the LoginModule by setting jaas-api-provision equal to "true".
Pre-Authentication Sample
diff --git a/docs/manual/src/docbook/security-filter-chain.xml b/docs/manual/src/docbook/security-filter-chain.xml
index 181af8763c..af192d7870 100644
--- a/docs/manual/src/docbook/security-filter-chain.xml
+++ b/docs/manual/src/docbook/security-filter-chain.xml
@@ -151,6 +151,13 @@
using it to install a Spring Security aware
HttpServletRequestWrapper into your servlet container
+
+ The JaasApiIntegrationFilter , if a
+ JaasAuthenticationToken is in the
+ SecurityContextHolder this will process the
+ FilterChain as the Subject in the
+ JaasAuthenticationToken
+
RememberMeAuthenticationFilter , so that if no earlier
authentication processing mechanism updated the
diff --git a/samples/jaas/jaas.gradle b/samples/jaas/jaas.gradle
index c6a6031108..0714ace274 100644
--- a/samples/jaas/jaas.gradle
+++ b/samples/jaas/jaas.gradle
@@ -13,7 +13,6 @@ dependencies {
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",
diff --git a/samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java b/samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java
index 213ed42b8b..2dca7dbeae 100644
--- a/samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java
+++ b/samples/jaas/src/main/java/samples/jaas/UsernameEqualsPasswordLoginModule.java
@@ -67,7 +67,7 @@ public class UsernameEqualsPasswordLoginModule implements LoginModule {
}
public boolean login() throws LoginException {
- if (username != null && !username.equals(password)) {
+ if (username == null || !username.equals(password)) {
throw new LoginException("username is not equal to password");
}
@@ -75,6 +75,9 @@ public class UsernameEqualsPasswordLoginModule implements LoginModule {
public String getName() {
return username;
}
+ public String toString() {
+ return "Principal [name="+getName()+"]";
+ }
});
return true;
}
diff --git a/samples/jaas/src/main/resources/applicationContext-security.xml b/samples/jaas/src/main/resources/applicationContext-security.xml
index d658938c7b..34b854e325 100644
--- a/samples/jaas/src/main/resources/applicationContext-security.xml
+++ b/samples/jaas/src/main/resources/applicationContext-security.xml
@@ -9,7 +9,7 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
-
+
diff --git a/samples/jaas/src/main/webapp/index.jsp b/samples/jaas/src/main/webapp/index.jsp
index f0dfc20ac8..06e2b29ee6 100644
--- a/samples/jaas/src/main/webapp/index.jsp
+++ b/samples/jaas/src/main/webapp/index.jsp
@@ -1,3 +1,5 @@
+<%@ page import="javax.security.auth.Subject" %>
+<%@ page import="java.security.AccessController" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
@@ -9,6 +11,9 @@ Anyone can view this page.
Your principal object is....: <%= request.getUserPrincipal() %>
+Subject.getSubject(AccessController.getContext()) is....: <%= Subject.getSubject(AccessController.getContext()) %>
+
+
You can currently access "/secure" URLs.
diff --git a/samples/jaas/src/main/webapp/secure/index.jsp b/samples/jaas/src/main/webapp/secure/index.jsp
index 553bc3a15a..6ddcc61e07 100644
--- a/samples/jaas/src/main/webapp/secure/index.jsp
+++ b/samples/jaas/src/main/webapp/secure/index.jsp
@@ -1,3 +1,5 @@
+<%@ page import="javax.security.auth.Subject" %>
+<%@ page import="java.security.AccessController" %>
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page import="org.springframework.security.core.Authentication" %>
<%@ page import="org.springframework.security.core.GrantedAuthority" %>
@@ -10,6 +12,15 @@
Security Debug Information
+<%
+
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if(subject != null) { %>
+
+ Subject.getSubject(AccessController.getContext()) is....: <%= subject %>
+
+ <%} %>
+
<%
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
diff --git a/web/src/main/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilter.java
new file mode 100644
index 0000000000..b2bdba2ce1
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilter.java
@@ -0,0 +1,163 @@
+/*
+ * 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.web.authentication.jaas;
+
+import java.io.IOException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.springframework.security.authentication.jaas.JaasAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+/**
+ *
+ * A Filter which attempts to obtain a JAAS Subject
+ * and continue the FilterChain running as that
+ * Subject.
+ *
+ *
+ * By using this Filter in conjunction with Spring's
+ * JaasAuthenticationProvider both Spring's
+ * SecurityContext and a JAAS Subject can be populated
+ * simultaneously. This is useful when integrating with code that requires a
+ * JAAS Subject to be populated.
+ *
+ *
+ * @author Rob Winch
+ * @see #doFilter(ServletRequest, ServletResponse, FilterChain)
+ * @see #obtainSubject(ServletRequest)
+ */
+public class JaasApiIntegrationFilter extends GenericFilterBean {
+ //~ Instance fields ================================================================================================
+
+ private boolean createEmptySubject;
+
+ //~ Methods ========================================================================================================
+
+ /**
+ *
+ * Attempts to obtain and run as a JAAS Subject using
+ * {@link #obtainSubject(ServletRequest)}.
+ *
+ *
+ *
+ * If the Subject is null and
+ * createEmptySubject is true, an empty, writeable
+ * Subject is used. This allows for the Subject to
+ * be populated at the time of login. If the Subject is
+ * null, the FilterChain continues with no
+ * additional processing. If the Subject is not
+ * null, the FilterChain is ran with
+ * {@link Subject#doAs(Subject, PrivilegedExceptionAction)} in conjunction
+ * with the Subject obtained.
+ *
+ */
+ public final void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
+ throws ServletException, IOException {
+
+ Subject subject = obtainSubject(request);
+ if (subject == null && createEmptySubject) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Subject returned was null and createEmtpySubject is true; creating new empty subject to run as.");
+ }
+ subject = new Subject();
+ }
+ if (subject == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Subject is null continue running with no Subject.");
+ }
+ chain.doFilter(request, response);
+ return;
+ }
+ final PrivilegedExceptionAction continueChain = new PrivilegedExceptionAction() {
+ public Object run() throws IOException, ServletException {
+ chain.doFilter(request, response);
+ return null;
+ }
+ };
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Running as Subject " + subject);
+ }
+ try {
+ Subject.doAs(subject, continueChain);
+ } catch (PrivilegedActionException e) {
+ throw new ServletException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ *
+ * Obtains the Subject to run as or null if no
+ * Subject is available.
+ *
+ *
+ * The default implementation attempts to obtain the Subject
+ * from the SecurityContext's Authentication. If
+ * it is of type JaasAuthenticationToken and is authenticated,
+ * the Subject is returned from it. Otherwise,
+ * null is returned.
+ *
+ *
+ * @param request
+ * the current ServletRequest
+ * @return the Subject to run as or null if no
+ * Subject is available.
+ */
+ protected Subject obtainSubject(ServletRequest request) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Attempting to obtainSubject using authentication : " + authentication);
+ }
+ if (authentication == null) {
+ return null;
+ }
+ if (!authentication.isAuthenticated()) {
+ return null;
+ }
+ if (!(authentication instanceof JaasAuthenticationToken)) {
+ return null;
+ }
+ JaasAuthenticationToken token = (JaasAuthenticationToken) authentication;
+ LoginContext loginContext = token.getLoginContext();
+ if (loginContext == null) {
+ return null;
+ }
+ return loginContext.getSubject();
+ }
+
+ /**
+ * Sets createEmptySubject. If the value is true,
+ * and {@link #obtainSubject(ServletRequest)} returns null, an
+ * empty, writeable Subject is created instead. Otherwise no
+ * Subject is used. The default is false.
+ *
+ * @param createEmptySubject
+ * the new value
+ */
+ public final void setCreateEmptySubject(boolean createEmptySubject) {
+ this.createEmptySubject = createEmptySubject;
+ }
+}
\ No newline at end of file
diff --git a/web/src/test/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilterTest.java b/web/src/test/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilterTest.java
new file mode 100644
index 0000000000..c6f187f25f
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/authentication/jaas/JaasApiIntegrationFilterTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.web.authentication.jaas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.HashMap;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextInputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+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.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authentication.jaas.JaasAuthenticationToken;
+import org.springframework.security.authentication.jaas.TestLoginModule;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Tests the JaasApiIntegrationFilter.
+ *
+ * @author Rob Winch
+ */
+public class JaasApiIntegrationFilterTest {
+ //~ Instance fields ================================================================================================
+ private JaasApiIntegrationFilter filter;
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+ private Authentication token;
+ private Subject authenticatedSubject;
+ private Configuration testConfiguration;
+ private CallbackHandler callbackHandler;
+ //~ Methods ========================================================================================================
+
+ @Before
+ public void onBeforeTests() throws Exception {
+ this.filter = new JaasApiIntegrationFilter();
+ this.request = new MockHttpServletRequest();
+ this.response = new MockHttpServletResponse();
+
+ authenticatedSubject = new Subject();
+ authenticatedSubject.getPrincipals().add(new Principal() {
+ public String getName() {
+ return "principal";
+ }
+ });
+ authenticatedSubject.getPrivateCredentials().add("password");
+ authenticatedSubject.getPublicCredentials().add("username");
+ callbackHandler = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback) {
+ ((NameCallback) callback).setName("user");
+ } else if (callback instanceof PasswordCallback) {
+ ((PasswordCallback) callback).setPassword("password".toCharArray());
+ } else if (callback instanceof TextInputCallback) {
+ // ignore
+ } else {
+ throw new UnsupportedCallbackException(callback, "Unrecognized Callback " + callback);
+ }
+ }
+ }
+ };
+ testConfiguration = new Configuration() {
+ public void refresh() {
+ }
+
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ return new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
+ LoginModuleControlFlag.REQUIRED, new HashMap()) };
+ }
+ };
+ LoginContext ctx = new LoginContext("SubjectDoAsFilterTest", authenticatedSubject, callbackHandler,
+ testConfiguration);
+ ctx.login();
+ token = new JaasAuthenticationToken("username", "password", AuthorityUtils.createAuthorityList("ROLE_ADMIN"),
+ ctx);
+
+ // just in case someone forgot to clear the context
+ SecurityContextHolder.clearContext();
+ }
+
+ @After
+ public void onAfterTests() {
+ SecurityContextHolder.clearContext();
+ }
+
+ /**
+ * Ensure a Subject was not setup in some other manner.
+ */
+ @Test
+ public void currentSubjectNull() {
+ assertNull(Subject.getSubject(AccessController.getContext()));
+ }
+
+ @Test
+ public void obtainSubjectNullAuthentication() {
+ assertNullSubject(filter.obtainSubject(request));
+ }
+
+ @Test
+ public void obtainSubjectNonJaasAuthentication() {
+ Authentication authentication = new TestingAuthenticationToken("un", "pwd");
+ authentication.setAuthenticated(true);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ assertNullSubject(filter.obtainSubject(request));
+ }
+
+ @Test
+ public void obtainSubjectNullLoginContext() {
+ token = new JaasAuthenticationToken("un", "pwd", AuthorityUtils.createAuthorityList("ROLE_ADMIN"), null);
+ SecurityContextHolder.getContext().setAuthentication(token);
+ assertNullSubject(filter.obtainSubject(request));
+ }
+
+ @Test
+ public void obtainSubjectNullSubject() throws Exception {
+ LoginContext ctx = new LoginContext("obtainSubjectNullSubject", null, callbackHandler, testConfiguration);
+ assertNull(ctx.getSubject());
+ token = new JaasAuthenticationToken("un", "pwd", AuthorityUtils.createAuthorityList("ROLE_ADMIN"), ctx);
+ SecurityContextHolder.getContext().setAuthentication(token);
+ assertNullSubject(filter.obtainSubject(request));
+ }
+
+ @Test
+ public void obtainSubject() throws Exception {
+ SecurityContextHolder.getContext().setAuthentication(token);
+ assertEquals(authenticatedSubject, filter.obtainSubject(request));
+ }
+
+ @Test
+ public void doFilterCurrentSubjectPopulated() throws Exception {
+ SecurityContextHolder.getContext().setAuthentication(token);
+ assertJaasSubjectEquals(authenticatedSubject);
+ }
+
+ @Test
+ public void doFilterAuthenticationNotAuthenticated() throws Exception {
+ // Authentication is null, so no Subject is populated.
+ token.setAuthenticated(false);
+ SecurityContextHolder.getContext().setAuthentication(token);
+ assertJaasSubjectEquals(null);
+ filter.setCreateEmptySubject(true);
+ assertJaasSubjectEquals(new Subject());
+ }
+
+ @Test
+ public void doFilterAuthenticationNull() throws Exception {
+ assertJaasSubjectEquals(null);
+ filter.setCreateEmptySubject(true);
+ assertJaasSubjectEquals(new Subject());
+ }
+
+ //~ Helper Methods ====================================================================================================
+
+ private void assertJaasSubjectEquals(final Subject expectedValue) throws Exception {
+ MockFilterChain chain = new MockFilterChain() {
+ public void doFilter(ServletRequest request, ServletResponse response) {
+ // See if the subject was updated
+ Subject currentSubject = Subject.getSubject(AccessController.getContext());
+ assertEquals(expectedValue, currentSubject);
+
+ // run so we know the chain was executed
+ super.doFilter(request, response);
+ }
+ };
+ filter.doFilter(request, response, chain);
+ // ensure that the chain was actually invoked
+ assertNotNull(chain.getRequest());
+ }
+
+ private void assertNullSubject(Subject subject) {
+ assertNull("Subject is expected to be null, but is not. Got " + subject, subject);
+ }
+}
\ No newline at end of file
diff --git a/web/web.gradle b/web/web.gradle
index 39bfb6daec..2cc1a35a73 100644
--- a/web/web.gradle
+++ b/web/web.gradle
@@ -13,7 +13,8 @@ dependencies {
provided 'javax.servlet:servlet-api:2.5'
- testCompile 'commons-codec:commons-codec:1.3',
+ testCompile project(':spring-security-core').sourceSets.test.classes,
+ 'commons-codec:commons-codec:1.3',
"org.springframework:spring-test:$springVersion"
testRuntime "hsqldb:hsqldb:$hsqlVersion"
}