/* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.web.authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockFilterConfig; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServicesTests; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.test.util.ReflectionTestUtils; /** * Tests {@link AbstractAuthenticationProcessingFilter}. * * @author Ben Alex * @author Luke Taylor * @author Rob Winch */ @SuppressWarnings("deprecation") public class AbstractAuthenticationProcessingFilterTests { SavedRequestAwareAuthenticationSuccessHandler successHandler; SimpleUrlAuthenticationFailureHandler failureHandler; // ~ Methods // ======================================================================================================== private MockHttpServletRequest createMockAuthenticationRequest() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setServletPath("/j_mock_post"); request.setScheme("http"); request.setServerName("www.example.com"); request.setRequestURI("/mycontext/j_mock_post"); request.setContextPath("/mycontext"); return request; } @Before public void setUp() throws Exception { successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setDefaultTargetUrl("/logged_in.jsp"); failureHandler = new SimpleUrlAuthenticationFailureHandler(); failureHandler.setDefaultFailureUrl("/failed.jsp"); SecurityContextHolder.clearContext(); } @After public void tearDown() throws Exception { SecurityContextHolder.clearContext(); } @Test public void testDefaultProcessesFilterUrlMatchesWithPathParameter() { MockHttpServletRequest request = createMockAuthenticationRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); MockAuthenticationFilter filter = new MockAuthenticationFilter(); filter.setFilterProcessesUrl("/login"); DefaultHttpFirewall firewall = new DefaultHttpFirewall(); request.setServletPath("/login;jsessionid=I8MIONOSTHOR"); // the firewall ensures that path parameters are ignored HttpServletRequest firewallRequest = firewall.getFirewalledRequest(request); assertThat(filter.requiresAuthentication(firewallRequest, response)).isTrue(); } @Test public void testFilterProcessesUrlVariationsRespected() throws Exception { // Setup our HTTP request MockHttpServletRequest request = createMockAuthenticationRequest(); request.setServletPath("/j_OTHER_LOCATION"); request.setRequestURI("/mycontext/j_OTHER_LOCATION"); // Setup our filter configuration MockFilterConfig config = new MockFilterConfig(null, null); // Setup our expectation that the filter chain will not be invoked, as we redirect // to defaultTargetUrl MockFilterChain chain = new MockFilterChain(false); MockHttpServletResponse response = new MockHttpServletResponse(); // Setup our test object, to grant access MockAuthenticationFilter filter = new MockAuthenticationFilter(true); filter.setFilterProcessesUrl("/j_OTHER_LOCATION"); filter.setAuthenticationSuccessHandler(successHandler); // Test filter.doFilter(request, response, chain); assertThat(response.getRedirectedUrl()).isEqualTo("/mycontext/logged_in.jsp"); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); assertThat( SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString()).isEqualTo( "test"); } @Test public void testGettersSetters() throws Exception { AbstractAuthenticationProcessingFilter filter = new MockAuthenticationFilter(); filter.setAuthenticationManager(mock(AuthenticationManager.class)); filter.setFilterProcessesUrl("/p"); filter.afterPropertiesSet(); assertThat(filter.getRememberMeServices()).isNotNull(); filter.setRememberMeServices(new TokenBasedRememberMeServices("key", new AbstractRememberMeServicesTests.MockUserDetailsService())); assertThat(filter.getRememberMeServices().getClass()).isEqualTo( TokenBasedRememberMeServices.class); assertThat(filter.getAuthenticationManager() != null).isTrue(); } @Test public void testIgnoresAnyServletPathOtherThanFilterProcessesUrl() throws Exception { // Setup our HTTP request MockHttpServletRequest request = createMockAuthenticationRequest(); request.setServletPath("/some.file.html"); request.setRequestURI("/mycontext/some.file.html"); // Setup our filter configuration MockFilterConfig config = new MockFilterConfig(null, null); // Setup our expectation that the filter chain will be invoked, as our request is // for a page the filter isn't monitoring MockFilterChain chain = new MockFilterChain(true); MockHttpServletResponse response = new MockHttpServletResponse(); // Setup our test object, to deny access MockAuthenticationFilter filter = new MockAuthenticationFilter(false); // Test filter.doFilter(request, response, chain); } @Test public void testNormalOperationWithDefaultFilterProcessesUrl() throws Exception { // Setup our HTTP request MockHttpServletRequest request = createMockAuthenticationRequest(); HttpSession sessionPreAuth = request.getSession(); // Setup our filter configuration MockFilterConfig config = new MockFilterConfig(null, null); // Setup our expectation that the filter chain will not be invoked, as we redirect // to defaultTargetUrl MockFilterChain chain = new MockFilterChain(false); MockHttpServletResponse response = new MockHttpServletResponse(); // Setup our test object, to grant access MockAuthenticationFilter filter = new MockAuthenticationFilter(true); filter.setFilterProcessesUrl("/j_mock_post"); filter.setSessionAuthenticationStrategy( mock(SessionAuthenticationStrategy.class)); filter.setAuthenticationSuccessHandler(successHandler); filter.setAuthenticationFailureHandler(failureHandler); filter.setAuthenticationManager(mock(AuthenticationManager.class)); filter.afterPropertiesSet(); // Test filter.doFilter(request, response, chain); assertThat(response.getRedirectedUrl()).isEqualTo("/mycontext/logged_in.jsp"); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); assertThat( SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString()).isEqualTo( "test"); // Should still have the same session assertThat(request.getSession()).isEqualTo(sessionPreAuth); } @Test public void testStartupDetectsInvalidAuthenticationManager() throws Exception { AbstractAuthenticationProcessingFilter filter = new MockAuthenticationFilter(); filter.setAuthenticationFailureHandler(failureHandler); successHandler.setDefaultTargetUrl("/"); filter.setAuthenticationSuccessHandler(successHandler); filter.setFilterProcessesUrl("/login"); try { filter.afterPropertiesSet(); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).isEqualTo( "authenticationManager must be specified"); } } @Test public void testStartupDetectsInvalidFilterProcessesUrl() throws Exception { AbstractAuthenticationProcessingFilter filter = new MockAuthenticationFilter(); filter.setAuthenticationFailureHandler(failureHandler); filter.setAuthenticationManager(mock(AuthenticationManager.class)); filter.setAuthenticationSuccessHandler(successHandler); try { filter.setFilterProcessesUrl(null); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).isEqualTo( "Pattern cannot be null or empty"); } } @Test public void testSuccessLoginThenFailureLoginResultsInSessionLosingToken() throws Exception { // Setup our HTTP request MockHttpServletRequest request = createMockAuthenticationRequest(); // Setup our filter configuration MockFilterConfig config = new MockFilterConfig(null, null); // Setup our expectation that the filter chain will not be invoked, as we redirect // to defaultTargetUrl MockFilterChain chain = new MockFilterChain(false); MockHttpServletResponse response = new MockHttpServletResponse(); // Setup our test object, to grant access MockAuthenticationFilter filter = new MockAuthenticationFilter(true); filter.setFilterProcessesUrl("/j_mock_post"); filter.setAuthenticationSuccessHandler(successHandler); // Test filter.doFilter(request, response, chain); assertThat(response.getRedirectedUrl()).isEqualTo("/mycontext/logged_in.jsp"); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); assertThat( SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString()).isEqualTo( "test"); // Now try again but this time have filter deny access // Setup our HTTP request // Setup our expectation that the filter chain will not be invoked, as we redirect // to authenticationFailureUrl chain = new MockFilterChain(false); response = new MockHttpServletResponse(); // Setup our test object, to deny access filter = new MockAuthenticationFilter(false); filter.setFilterProcessesUrl("/j_mock_post"); filter.setAuthenticationFailureHandler(failureHandler); // Test filter.doFilter(request, response, chain); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); } @Test public void testSuccessfulAuthenticationInvokesSuccessHandlerAndSetsContext() throws Exception { // Setup our HTTP request MockHttpServletRequest request = createMockAuthenticationRequest(); // Setup our filter configuration MockFilterConfig config = new MockFilterConfig(null, null); // Setup our expectation that the filter chain will be invoked, as we want to go // to the location requested in the session MockFilterChain chain = new MockFilterChain(true); MockHttpServletResponse response = new MockHttpServletResponse(); // Setup our test object, to grant access MockAuthenticationFilter filter = new MockAuthenticationFilter(true); filter.setFilterProcessesUrl("/j_mock_post"); AuthenticationSuccessHandler successHandler = mock( AuthenticationSuccessHandler.class); filter.setAuthenticationSuccessHandler(successHandler); // Test filter.doFilter(request, response, chain); verify(successHandler).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class), any(Authentication.class)); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); } @Test public void testFailedAuthenticationInvokesFailureHandler() throws Exception { // Setup our HTTP request MockHttpServletRequest request = createMockAuthenticationRequest(); // Setup our filter configuration MockFilterConfig config = new MockFilterConfig(null, null); // Setup our expectation that the filter chain will not be invoked, as we redirect // to authenticationFailureUrl MockFilterChain chain = new MockFilterChain(false); MockHttpServletResponse response = new MockHttpServletResponse(); // Setup our test object, to deny access MockAuthenticationFilter filter = new MockAuthenticationFilter(false); AuthenticationFailureHandler failureHandler = mock( AuthenticationFailureHandler.class); filter.setAuthenticationFailureHandler(failureHandler); // Test filter.doFilter(request, response, chain); verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); } /** * SEC-571 */ @Test public void testNoSessionIsCreatedIfAllowSessionCreationIsFalse() throws Exception { MockHttpServletRequest request = createMockAuthenticationRequest(); MockFilterConfig config = new MockFilterConfig(null, null); MockFilterChain chain = new MockFilterChain(true); MockHttpServletResponse response = new MockHttpServletResponse(); // Reject authentication, so exception would normally be stored in session MockAuthenticationFilter filter = new MockAuthenticationFilter(false); failureHandler.setAllowSessionCreation(false); filter.setAuthenticationFailureHandler(failureHandler); filter.doFilter(request, response, chain); assertThat(request.getSession(false)).isNull(); } /** * SEC-462 */ @Test public void testLoginErrorWithNoFailureUrlSendsUnauthorizedStatus() throws Exception { MockHttpServletRequest request = createMockAuthenticationRequest(); MockFilterConfig config = new MockFilterConfig(null, null); MockFilterChain chain = new MockFilterChain(true); MockHttpServletResponse response = new MockHttpServletResponse(); MockAuthenticationFilter filter = new MockAuthenticationFilter(false); successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/"); filter.setAuthenticationSuccessHandler(successHandler); filter.doFilter(request, response, chain); assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } /** * SEC-1919 */ @Test public void loginErrorWithInternAuthenticationServiceExceptionLogsError() throws Exception { MockHttpServletRequest request = createMockAuthenticationRequest(); MockFilterChain chain = new MockFilterChain(true); MockHttpServletResponse response = new MockHttpServletResponse(); Log logger = mock(Log.class); MockAuthenticationFilter filter = new MockAuthenticationFilter(false); ReflectionTestUtils.setField(filter, "logger", logger); filter.exceptionToThrow = new InternalAuthenticationServiceException( "Mock requested to do so"); successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/"); filter.setAuthenticationSuccessHandler(successHandler); filter.doFilter(request, response, chain); verify(logger).error(anyString(), eq(filter.exceptionToThrow)); assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } /** * https://github.com/spring-projects/spring-security/pull/3905 */ @Test(expected = IllegalArgumentException.class) public void setRememberMeServicesShouldntAllowNulls() { AbstractAuthenticationProcessingFilter filter = new MockAuthenticationFilter(); filter.setRememberMeServices(null); } // ~ Inner Classes // ================================================================================================== private class MockAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private AuthenticationException exceptionToThrow; private boolean grantAccess; public MockAuthenticationFilter(boolean grantAccess) { this(); setRememberMeServices(new NullRememberMeServices()); this.grantAccess = grantAccess; this.exceptionToThrow = new BadCredentialsException( "Mock requested to do so"); } private MockAuthenticationFilter() { super("/j_mock_post"); } public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (grantAccess) { return new UsernamePasswordAuthenticationToken("test", "test", AuthorityUtils.createAuthorityList("TEST")); } else { throw exceptionToThrow; } } } private class MockFilterChain implements FilterChain { private boolean expectToProceed; public MockFilterChain(boolean expectToProceed) { this.expectToProceed = expectToProceed; } public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (expectToProceed) { } else { fail("Did not expect filter chain to proceed"); } } } }