Onboarding filter in Spring Boot and Spring Security

Viewed 75

I'm trying to create a filter in order to redirect logged users to the onboarding page in case they haven't completed the process before.

This is my filter so far:

@Component
@Order(110)
public class OnboardingFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    // cast the request and response to HTTP
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    HttpSession session = httpRequest.getSession(false);

    SecurityContextImpl securityContext = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");

    // if there's a logged user
    if (securityContext != null) {
      UserPrincipal principal = (UserPrincipal) securityContext.getAuthentication().getPrincipal();
        
      if (!principal.hasOnboarded()) {
        httpResponse.sendRedirect("/onboarding");
      }
    }

    // continue with the filter chain
    chain.doFilter(httpRequest, httpResponse);
  }

I've tried different values for @Order but in every case the http response generates a downloadable content instead of showing the actual requested URL or sending the redirect. Any ideas?

I'm getting this exception:

Caused by: java.lang.IllegalStateException: Committed
    at org.eclipse.jetty.server.HttpChannel.resetBuffer(HttpChannel.java:917)
HttpChannel.java:917
    at org.eclipse.jetty.server.HttpOutput.resetBuffer(HttpOutput.java:1423)
HttpOutput.java:1423
    at org.eclipse.jetty.server.Response.resetBuffer(Response.java:1182)
Response.java:1182
    at org.eclipse.jetty.server.Response.sendRedirect(Response.java:534)
Response.java:534
    at org.eclipse.jetty.server.Response.sendRedirect(Response.java:543)
Response.java:543
    at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
    at org.springframework.security.web.firewall.FirewalledResponse.sendRedirect(FirewalledResponse.java:43)
FirewalledResponse.java:43
    at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
    at org.springframework.security.web.util.OnCommittedResponseWrapper.sendRedirect(OnCommittedResponseWrapper.java:135)
OnCommittedResponseWrapper.java:135
    at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
    at org.springframework.security.web.util.OnCommittedResponseWrapper.sendRedirect(OnCommittedResponseWrapper.java:135)
OnCommittedResponseWrapper.java:135
    at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
    at org.springframework.web.servlet.view.RedirectView.sendRedirect(RedirectView.java:627)
RedirectView.java:627
    at org.springframework.web.servlet.view.RedirectView.renderMergedOutputModel(RedirectView.java:314)
RedirectView.java:314
    at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316)
AbstractView.java:316
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1373)
DispatcherServlet.java:1373
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1118)
DispatcherServlet.java:1118
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)
DispatcherServlet.java:1057
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
DispatcherServlet.java:943
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
FrameworkServlet.java:1006
    ... 94 common frames omitted
2 Answers

What about adding a return in:

httpResponse.sendRedirect("/onboarding");
return;

Otherwise you are continuing with the filter chain:

chain.doFilter(httpRequest, httpResponse);

Not 100% sure if this is already solving your issue, but might be also a problem.

I finally solved this by implementing an Spring context-aware GenericFilterBean as follows:

public class OnboardingFilter extends GenericFilterBean {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    if (SecurityContextHolder.getContext().getAuthentication() != null) {
      // some business logic
      httpResponse.sendRedirect("/onboarding");
    }
    else {
      chain.doFilter(httpRequest, httpResponse);
    }
  }
}

Don't forget to add it to the security filter chain:

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.addFilterAfter(new OnboardingFilter(), UsernamePasswordAuthenticationFilter.class)...
Related