diff --git a/itest/context/pom.xml b/itest/context/pom.xml index f05a1ef1d7..6185a25f62 100644 --- a/itest/context/pom.xml +++ b/itest/context/pom.xml @@ -18,5 +18,10 @@ 4.4 test + + javax.servlet + servlet-api + test + diff --git a/itest/context/src/test/java/org/springframework/security/performance/FilterChainPerformanceTests.java b/itest/context/src/test/java/org/springframework/security/performance/FilterChainPerformanceTests.java new file mode 100644 index 0000000000..266b6800bb --- /dev/null +++ b/itest/context/src/test/java/org/springframework/security/performance/FilterChainPerformanceTests.java @@ -0,0 +1,136 @@ +package org.springframework.security.performance; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpSession; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.context.HttpSessionSecurityContextRepository; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.security.util.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.StopWatch; + +import edu.emory.mathcs.backport.java.util.Arrays; + +/** + * + * @author Luke Taylor + * @version $Id$ + * @since 2.0 + */ +@ContextConfiguration(locations={"/filter-chain-performance-app-context.xml"}) +@RunWith(SpringJUnit4ClassRunner.class) +public class FilterChainPerformanceTests { + private static final int N_INVOCATIONS = 1000; + private static final int N_AUTHORITIES = 200; + private static StopWatch sw = new StopWatch("Filter Chain Performance Tests"); + + private final UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken("bob", "bobspassword", createRoles(N_AUTHORITIES)); + private HttpSession session; + + @Autowired + @Qualifier("fcpMinimalStack") + private FilterChainProxy minimalStack; + + @Autowired + @Qualifier("fcpFullStack") + private FilterChainProxy fullStack; + + @Before + public void createAuthenticatedSession() { + session = new MockHttpSession(); + SecurityContextHolder.getContext().setAuthentication(user); + session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); + SecurityContextHolder.clearContext(); + } + + @After + public void clearContext() { + SecurityContextHolder.clearContext(); + } + + @AfterClass + public static void dumpStopWatch() { + System.out.println(sw.prettyPrint()); + } + + private MockHttpServletRequest createRequest(String url) { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setSession(session); + request.setServletPath(url); + request.setMethod("GET"); + return request; + } + + private void runWithStack(FilterChainProxy stack) throws Exception { + for(int i = 0; i < N_INVOCATIONS; i++) { + MockHttpServletRequest request = createRequest("/somefile.html"); + stack.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); + session = request.getSession(); + } + } + + @Test + public void minimalStackInvocation() throws Exception { + sw.start("Run with Minimal Filter Stack"); + runWithStack(minimalStack); + sw.stop(); + } + + @Test + public void fullStackInvocation() throws Exception { + sw.start("Run with Full Filter Stack"); + runWithStack(fullStack); + sw.stop(); + } + + /** + * Creates data from 1 to N_AUTHORITIES in steps of 10, performing N_INVOCATIONS for each + */ + @Test + public void provideDataOnScalingWithNumberOfAuthoritiesUserHas() throws Exception { + StopWatch sw = new StopWatch("Scaling with nAuthorities"); + for (int user=0; user < N_AUTHORITIES/10; user ++) { + int nAuthorities = user == 0 ? 1 : user*10; + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob", "bobspassword", createRoles(nAuthorities))); + session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); + SecurityContextHolder.clearContext(); + sw.start(Integer.toString(nAuthorities) + " authorities"); + runWithStack(minimalStack); + System.out.println(sw.shortSummary()); + sw.stop(); + } + System.out.println(sw.prettyPrint()); + } + + private List createRoles(int howMany) { + // This is always the worst case scenario - the required role is ROLE_1, but they are created in reverse order + GrantedAuthority[] roles = new GrantedAuthority[howMany]; + + for (int i = howMany - 1; i >=0 ; i--) { + roles[i] = new GrantedAuthorityImpl("ROLE_" + i); + } + + return Arrays.asList(roles); + } +} diff --git a/itest/context/src/test/resources/filter-chain-performance-app-context.xml b/itest/context/src/test/resources/filter-chain-performance-app-context.xml new file mode 100644 index 0000000000..9974b27ab1 --- /dev/null +++ b/itest/context/src/test/resources/filter-chain-performance-app-context.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/itest/context/src/test/resources/log4j.properties b/itest/context/src/test/resources/log4j.properties new file mode 100644 index 0000000000..6f17212f16 --- /dev/null +++ b/itest/context/src/test/resources/log4j.properties @@ -0,0 +1,13 @@ +# Logging +# +# $Id$ + +log4j.rootLogger=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=[%p,%c{1}] %m%n + +log4j.logger.org.springframework.security=WARN +log4j.logger.org.springframework.ldap=WARN +log4j.logger.org.apache.directory=ERROR