2

I just discovered the new Spring Security 4 test annotation @WithMockUser, but I cannot have it working for my Selenium test. The thing is, this annotation creates a mock SecurityContext, but a new one is created by the HttpSessionSecurityContextRepository because Selenium runs the test based on a real browser. Could I somehow tell Spring to use the mock SecurityContext as the next session security context?

Thanks!

gdrt
  • 3,160
  • 4
  • 37
  • 56
Romain F.
  • 766
  • 10
  • 15

2 Answers2

2

I found to way to authenticate a mock user with a new filter in the test classpath:

@Component
public class MockUserFilter extends GenericFilterBean {

    @Autowired
    private UserDetailsService userDetailsService;

    private SecurityContext securityContext;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (securityContext != null) {
            SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(request);

            HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response);
            securityContextRepository.loadContext(requestResponseHolder);

            request = requestResponseHolder.getRequest();
            response = requestResponseHolder.getResponse();

            securityContextRepository.saveContext(securityContext, request, response);

            securityContext = null;
        }
        chain.doFilter(request, response);
    }

    public void authenticateNextRequestAs(String username) {
        UserDetails principal = userDetailsService.loadUserByUsername(username);
        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
        securityContext = SecurityContextHolder.createEmptyContext();
        securityContext.setAuthentication(authentication);
    }
}

It is inspired from SecurityMockMvcRequestPostProcessors and WithUserDetailsSecurityContextFactory.

I could not use @WithUserDetails annotation because I run Cucumber tests, but with this filter I can mock an authentication for the next request in one line: testSecurityFilter.authenticateNextRequestAs("username");

Romain F.
  • 766
  • 10
  • 15
0

I'm adding this answer because while the accepted answer helped me in forming a solution, I had to make some changes to get this to work. This answer also helped me in getting it working: https://stackoverflow.com/a/8336233/2688076

Here is my MockUserFilter:

        @Component("MockUserFilter")
        public class MockUserFilter extends GenericFilterBean {
            @Autowired
            private UserDetailService userDetailService;

            private SecurityContext securityContext;

            @Autowired
            private AuthenticationProvider authenticationProvider;

            public void setUserDetailService(UserDetailService userDetailService) {
                this.userDetailService = userDetailService;
            }
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                HttpServletRequest servletRequest = (HttpServletRequest) request;
                HttpServletResponse servletResponse = (HttpServletResponse) response;

                if (securityContext != null) {
                    SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(servletRequest);
                    HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(servletRequest, servletResponse);
                    securityContextRepository.loadContext(requestResponseHolder);
                    servletRequest = requestResponseHolder.getRequest();
                    servletResponse = requestResponseHolder.getResponse();
                    securityContextRepository.saveContext(securityContext, servletRequest, servletResponse);
                    securityContext = null;
                }

                chain.doFilter(request, response);
            }

             public void authenticateNextRequestAs(String username, ServletRequest request) {
                 UserDetails principal = userDetailService.loadUserByUsername(username);
                 Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
                 securityContext = SecurityContextHolder.createEmptyContext();
                 securityContext.setAuthentication(authentication);
                 SecurityContextHolder.getContext().setAuthentication(authentication);

                 HttpSession session = ((HttpServletRequest) request).getSession(true);
                 session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
            }
        }

In addition to this I also had to remove my casAuthenticationFilter from the filter chain to get this working. I use a properties value to enable/disable this.

I'm relatively new to Spring and Spring security so any comments on this solution are welcome. I'm not sure how "good" or "bad" this solution is.

One thing to keep in mind is that this is a solution for local testing or testing in a secure environment, not one that you'd want in a dev environment.

Community
  • 1
  • 1
ruku
  • 55
  • 7