/*
* 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");
}
}
}
}