diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index 9c1e20d86b..439020d63a 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -115,6 +115,7 @@ final class AuthenticationConfigBuilder { private BeanDefinition jeeFilter; private BeanReference jeeProviderRef; private RootBeanDefinition preAuthEntryPoint; + private BeanMetadataElement mainEntryPoint; private BeanDefinition logoutFilter; @SuppressWarnings("rawtypes") @@ -499,6 +500,10 @@ final class AuthenticationConfigBuilder { return logoutHandlers; } + BeanMetadataElement getEntryPointBean() { + return mainEntryPoint; + } + void createAnonymousFilter() { Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS); @@ -556,7 +561,8 @@ final class AuthenticationConfigBuilder { BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class); etfBuilder.addPropertyValue("accessDeniedHandler", createAccessDeniedHandler(httpElt, pc)); assert requestCache != null; - etfBuilder.addConstructorArgValue(selectEntryPoint()); + mainEntryPoint = selectEntryPoint(); + etfBuilder.addConstructorArgValue(mainEntryPoint); etfBuilder.addConstructorArgValue(requestCache); etf = etfBuilder.getBeanDefinition(); 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 fc748b85de..a568307d4e 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 @@ -23,6 +23,7 @@ import java.util.List; import javax.servlet.ServletRequest; +import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -146,7 +147,7 @@ class HttpConfigurationBuilder { createSessionManagementFilters(); createWebAsyncManagerFilter(); createRequestCacheFilter(); - createServletApiFilter(); + createServletApiFilter(authenticationManager); createJaasApiFilter(); createChannelProcessingFilter(); createFilterSecurityInterceptor(authenticationManager); @@ -154,8 +155,19 @@ class HttpConfigurationBuilder { @SuppressWarnings("rawtypes") void setLogoutHandlers(ManagedList logoutHandlers) { - if(logoutHandlers != null && concurrentSessionFilter != null) { - concurrentSessionFilter.getPropertyValues().add("logoutHandlers", logoutHandlers); + if(logoutHandlers != null) { + if(concurrentSessionFilter != null) { + concurrentSessionFilter.getPropertyValues().add("logoutHandlers", logoutHandlers); + } + if(servApiFilter != null) { + servApiFilter.getPropertyValues().add("logoutHandlers", logoutHandlers); + } + } + } + + void setEntryPoint(BeanMetadataElement entryPoint) { + if(servApiFilter != null) { + servApiFilter.getPropertyValues().add("authenticationEntryPoint", entryPoint); } } @@ -363,7 +375,7 @@ class HttpConfigurationBuilder { } // Adds the servlet-api integration filter if required - private void createServletApiFilter() { + private void createServletApiFilter(BeanReference authenticationManager) { final String ATT_SERVLET_API_PROVISION = "servlet-api-provision"; final String DEF_SERVLET_API_PROVISION = "true"; @@ -374,6 +386,7 @@ class HttpConfigurationBuilder { if ("true".equals(provideServletApi)) { servApiFilter = new RootBeanDefinition(SecurityContextHolderAwareRequestFilter.class); + servApiFilter.getPropertyValues().add("authenticationManager", authenticationManager); } } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index 8dc80023f1..3d3fda7436 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -140,6 +140,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { httpBldr.getSessionStrategy(), portMapper, portResolver); httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers()); + httpBldr.setEntryPoint(authBldr.getEntryPointBean()); authenticationProviders.addAll(authBldr.getProviders()); diff --git a/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy new file mode 100644 index 0000000000..35c26ed69c --- /dev/null +++ b/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2012 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.config.http + +import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML + +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException +import org.springframework.security.TestDataSource +import org.springframework.security.authentication.ProviderManager +import org.springframework.security.authentication.RememberMeAuthenticationProvider +import org.springframework.security.config.ldap.ContextSourceSettingPostProcessor; +import org.springframework.security.core.userdetails.MockUserDetailsService +import org.springframework.security.util.FieldUtils +import org.springframework.security.web.access.ExceptionTranslationFilter +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter +import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler; +import org.springframework.security.web.authentication.logout.LogoutFilter +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler +import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl +import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl +import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices +import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter +import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter + +/** + * + * @author Rob Winch + */ +class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTests { + + def withAutoConfig() { + httpAutoConfig () { + + } + createAppContext(AUTH_PROVIDER_XML) + + def securityContextAwareFilter = getFilter(SecurityContextHolderAwareRequestFilter) + + expect: + securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl + securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager + securityContextAwareFilter.logoutHandlers.size() == 1 + securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler + } + + def explicitEntryPoint() { + xml.http() { + 'http-basic'('entry-point-ref': 'ep') + } + bean('ep', BasicAuthenticationEntryPoint.class.name, ['realmName':'whocares'],[:]) + createAppContext(AUTH_PROVIDER_XML) + + def securityContextAwareFilter = getFilter(SecurityContextHolderAwareRequestFilter) + + expect: + securityContextAwareFilter.authenticationEntryPoint == getFilter(ExceptionTranslationFilter).authenticationEntryPoint + securityContextAwareFilter.authenticationManager == getFilter(BasicAuthenticationFilter).authenticationManager + securityContextAwareFilter.logoutHandlers == null + } + + def formLogin() { + xml.http() { + 'form-login'() + } + createAppContext(AUTH_PROVIDER_XML) + + def securityContextAwareFilter = getFilter(SecurityContextHolderAwareRequestFilter) + + expect: + securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl + securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager + securityContextAwareFilter.logoutHandlers == null + } + + def multiHttp() { + xml.http('authentication-manager-ref' : 'authManager', 'pattern' : '/first/**') { + 'form-login'('login-page' : '/login') + 'logout'('invalidate-session' : 'true') + } + xml.http('authentication-manager-ref' : 'authManager2') { + 'form-login'('login-page' : '/login2') + 'logout'('invalidate-session' : 'false') + } + + String secondAuthManager = AUTH_PROVIDER_XML.replace("alias='authManager'", "id='authManager2'") + createAppContext(AUTH_PROVIDER_XML + secondAuthManager) + + def securityContextAwareFilter = getFilters('/first/filters').find { it instanceof SecurityContextHolderAwareRequestFilter } + def secondSecurityContextAwareFilter = getFilter(SecurityContextHolderAwareRequestFilter) + + expect: + securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == '/login' + securityContextAwareFilter.authenticationManager == getFilters('/first/filters').find { it instanceof UsernamePasswordAuthenticationFilter}.authenticationManager + securityContextAwareFilter.authenticationManager.parent == appContext.getBean('authManager') + securityContextAwareFilter.logoutHandlers.size() == 1 + securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler + securityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == true + + secondSecurityContextAwareFilter.authenticationEntryPoint.loginFormUrl == '/login2' + secondSecurityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager + secondSecurityContextAwareFilter.authenticationManager.parent == appContext.getBean('authManager2') + securityContextAwareFilter.logoutHandlers.size() == 1 + secondSecurityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler + secondSecurityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == false + } + + def logoutCustom() { + xml.http() { + 'form-login'('login-page' : '/login') + 'logout'('invalidate-session' : 'false', 'logout-success-url' : '/login?logout', 'delete-cookies' : 'JSESSIONID') + } + createAppContext(AUTH_PROVIDER_XML) + + def securityContextAwareFilter = getFilter(SecurityContextHolderAwareRequestFilter) + + expect: + securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl + securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager + securityContextAwareFilter.logoutHandlers.size() == 2 + securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler + securityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == false + securityContextAwareFilter.logoutHandlers[1].class == CookieClearingLogoutHandler + securityContextAwareFilter.logoutHandlers[1].cookiesToClear == ['JSESSIONID'] + } +} diff --git a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet25RequestFactory.java b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet25RequestFactory.java index 52e74871ef..487b4923c0 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet25RequestFactory.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet25RequestFactory.java @@ -1,7 +1,26 @@ +/* + * Copyright 2002-2012 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.servletapi; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +/** + * Creates a {@link SecurityContextHolderAwareRequestWrapper} + * + * @author Rob Winch + * @see SecurityContextHolderAwareRequestWrapper + */ final class HttpServlet25RequestFactory implements HttpServletRequestFactory { private final String rolePrefix; @@ -9,7 +28,7 @@ final class HttpServlet25RequestFactory implements HttpServletRequestFactory { this.rolePrefix = rolePrefix; } - public HttpServletRequest create(HttpServletRequest request) { + public HttpServletRequest create(HttpServletRequest request, HttpServletResponse response) { return new SecurityContextHolderAwareRequestWrapper(request, rolePrefix) ; } } diff --git a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java index 1eae4378f1..813b4406d9 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java @@ -12,6 +12,10 @@ */ package org.springframework.security.web.servletapi; +import java.io.IOException; +import java.security.Principal; +import java.util.List; + import javax.servlet.AsyncContext; import javax.servlet.AsyncListener; import javax.servlet.ServletContext; @@ -19,23 +23,121 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.concurrent.DelegatingSecurityContextRunnable; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.logout.LogoutHandler; +/** + * Provides integration with the Servlet 3 APIs in addition to the ones found in {@link HttpServlet25RequestFactory}. + * The additional methods that are integrated with can be found below: + * + *
+ * Sets the {@link AuthenticationEntryPoint} used when integrating {@link HttpServletRequest} with Servlet 3 APIs. + * Specifically, it will be used when {@link HttpServletRequest#authenticate(HttpServletResponse)} is called and the + * user is not authenticated. + *
+ *+ * If the value is null (default), then the default container behavior will be be retained when invoking + * {@link HttpServletRequest#authenticate(HttpServletResponse)}. + *
+ * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use when invoking + * {@link HttpServletRequest#authenticate(HttpServletResponse)} if the user is not authenticated. + */ + + public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; } - private static class Servlet3SecurityContextHolderAwareRequestWrapper extends SecurityContextHolderAwareRequestWrapper { - public Servlet3SecurityContextHolderAwareRequestWrapper(HttpServletRequest request, String rolePrefix) { + /** + *+ * Sets the {@link AuthenticationManager} used when integrating {@link HttpServletRequest} with Servlet 3 APIs. + * Specifically, it will be used when {@link HttpServletRequest#login(String, String)} is invoked to determine if + * the user is authenticated. + *
+ *+ * If the value is null (default), then the default container behavior will be retained when invoking + * {@link HttpServletRequest#login(String, String)}. + *
+ * + * @param authenticationManager the {@link AuthenticationManager} to use when invoking + * {@link HttpServletRequest#login(String, String)} + */ + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + /** + *+ * Sets the {@link LogoutHandler}s used when integrating with {@link HttpServletRequest} with Servlet 3 APIs. + * Specifically it will be used when {@link HttpServletRequest#logout()} is invoked in order to log the user out. So + * long as the {@link LogoutHandler}s do not commit the {@link HttpServletResponse} (expected), then the user is in + * charge of handling the response. + *
+ *+ * If the value is null (default), the default container behavior will be retained when invoking + * {@link HttpServletRequest#logout()}. + *
+ * + * @param logoutHandlers the {@link ListFilter
which populates the ServletRequest
with a request wrapper
* which implements the servlet API security methods.
* - * The wrapper class used is {@link SecurityContextHolderAwareRequestWrapper}. + * In pre servlet 3 environment the wrapper class used is {@link SecurityContextHolderAwareRequestWrapper}. See its javadoc for the methods that are implemented. + *
+ *+ * In a servlet 3 environment {@link SecurityContextHolderAwareRequestWrapper} is extended to provide the following additional methods: + *
+ *+ * Sets the {@link AuthenticationEntryPoint} used when integrating {@link HttpServletRequest} with Servlet 3 APIs. + * Specifically, it will be used when {@link HttpServletRequest#authenticate(HttpServletResponse)} is called and the + * user is not authenticated. + *
+ *+ * If the value is null (default), then the default container behavior will be be retained when invoking + * {@link HttpServletRequest#authenticate(HttpServletResponse)}. + *
+ * + * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use when invoking + * {@link HttpServletRequest#authenticate(HttpServletResponse)} if the user is not authenticated. + * + * @throws IllegalStateException if the Servlet 3 APIs are not found on the classpath + */ + public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; + } + + /** + *+ * Sets the {@link AuthenticationManager} used when integrating {@link HttpServletRequest} with Servlet 3 APIs. + * Specifically, it will be used when {@link HttpServletRequest#login(String, String)} is invoked to determine if + * the user is authenticated. + *
+ *+ * If the value is null (default), then the default container behavior will be retained when invoking + * {@link HttpServletRequest#login(String, String)}. + *
+ * + * @param authenticationManager the {@link AuthenticationManager} to use when invoking + * {@link HttpServletRequest#login(String, String)} + * + * @throws IllegalStateException if the Servlet 3 APIs are not found on the classpath + */ + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + /** + *+ * Sets the {@link LogoutHandler}s used when integrating with {@link HttpServletRequest} with Servlet 3 APIs. + * Specifically it will be used when {@link HttpServletRequest#logout()} is invoked in order to log the user out. So + * long as the {@link LogoutHandler}s do not commit the {@link HttpServletResponse} (expected), then the user is in + * charge of handling the response. + *
+ *+ * If the value is null (default), the default container behavior will be retained when invoking + * {@link HttpServletRequest#logout()}. + *
+ * + * @param logoutHandlers the {@link ListHttpServletRequestWrapper
, which uses the
* SecurityContext
-defined Authentication
object to implement the servlet API security
- * methods {@link SecurityContextHolderAwareRequestWrapper#isUserInRole(String)} and {@link
- * HttpServletRequestWrapper#getRemoteUser()}.
+ * methods:
+ *
+ *