spring-bootspring-mvcspring-securityspring-sessionspring-filter

How to forcefully end filter-chain in an early servlet/spring boot filter


Having a multi-tenancy application, I want to end any request without a tenant very early.

Setup / Scenario

I run spring boot (web) with spring session and spring security.

In my case, I have several strategies to determine the tenant, TenantFromX, TenantFromY .. all running on order Ordered.HIGHEST_PRECEDENCE - so as early as possible.

On order Ordered.HIGHEST_PRECEDENCE+1 I run the TenantMustExistFilter filter to validate, that any strategy actually did match / find a tenant definition

@Log4j2
@Component
@RequiredArgsConstructor
// After TenantFromX(min) but before SessionRepositoryFilter(Session) which is min+50
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class TenantMandatoryFilter extends GenericFilterBean
{
  @Override
  public void doFilter(
    ServletRequest request, ServletResponse response,
    FilterChain filterChain
  ) throws ServletException, IOException
  {
    if (TenantStore.getInitMode().isEmpty()) {
      throw new NoTenantException("No tenant identified for request - request blocked");
    }

    try {
      filterChain.doFilter(request, response);
    } finally {
      TenantStore.clear();
    }
  }
}

Question / Problem

Eventhough I throw an Runtime exception NoTenantException all other filters after that filter are called anyway.

For example SessionRepositoryFilter is called on Ordered.HIGHEST_PRECEDENCE+50 and then the spring security FilterProxyChain is triggered.

How do I effectively stop the chain after TenantMandatoryFilter has failed to validate? I expected either a return; or and Exception or just not calling filterChain.doFilter(request, response); to be just enough to end the chain execution.

Do I have a misunderstanding of the process or the idea here?

Attempts

I can successfully end the chain by replacing

if (TenantStore.getInitMode().isEmpty()) {
  throw new NoTenantException("No tenant identified for request - request blocked");
}

with

if (TenantStore.getInitMode().isEmpty()) {
  response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  return;
}

Interestingly the below will not work, even though it seems semantically more correct. It does not work means, it will call the SessionRepositoryFilter and the FilterChainProxy after that nevertheless.

if (TenantStore.getInitMode().isEmpty()) {
  response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid tenant");
  return;
}

Which I assume is since sendError sets the response to suspended to complete any other chain (which is what I do not want).

It still does not seem right, so how to do this the right way? Or is this a conceptual flaw altogether?


Solution

  • Sending an error, either via an explicit call or by throwing an exception, will cause the request to be dispatched to the application’s error page handling which will invoke the filters again for that dispatch. Setting the status is the right thing to do.