title: Spring Security Access Denied Handler is called when the HttpServletResponse is already committed
body: <p>I have an HTTP API, protected with Spring Security and JWT.<br>
  I get a 401 when I'm trying to access a protected resource.<br>
  I get the resource if I'm authenticated (JWT is valid) and I have the correct role. The resource is protected with <code>@PreAuthorize("hasRole('USER')")</code>.  </p>
  
  <p>The issue I have is that when I don't have the correct role I'd like to return a 403 (in the following code it is a 401 for the sake of testing). 
  But right know I get a 500 because of the AccessDeniedException which is thrown when the role is incorrect.<br>
  The weird thing is that it goes to my JwtAccessDeniedHandler custom code but the response is already committed (isCommitted() == true) so whenever I try to set the status etc it does nothing.<br>
  Do you have any ideas about what could be misconfigured or missing?</p>
  
  <p>Config:</p>
  
  <pre><code>@Slf4j
  @EnableWebSecurity
  @EnableGlobalMethodSecurity(
          prePostEnabled = true,
          securedEnabled = true,
          jsr250Enabled = true
  )
  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  
      @Autowired
      private ObjectMapper objectMapper;
  
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http
                  .addFilterBefore(
                          jwtAuthenticationFilter(joseHelper(jsonWebKey())),
                          UsernamePasswordAuthenticationFilter.class)
                  .authorizeRequests()
                  .antMatchers("/auth/**").permitAll()
                  .anyRequest().authenticated()
                  .and()
                  .sessionManagement()
                  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                  .and()
                  .csrf().disable()
                  .exceptionHandling()
                  .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                  .accessDeniedHandler(new JwtAccessDeniedHandler());
      }
  
      @Bean
      public JwtAuthenticationFilter jwtAuthenticationFilter(JoseHelper joseHelper) {
          return new JwtAuthenticationFilter(joseHelper);
      }
  
      @Bean
      public JoseHelper joseHelper(PublicJsonWebKey key) {
          return new JoseHelper(key);
      }
  
      @Bean
      public PublicJsonWebKey jsonWebKey() throws IOException, JoseException {
          return RsaJwkGenerator.generateJwk(2048);
      }
  
      private void sendUnauthorized(HttpServletResponse httpServletResponse) throws IOException {
          httpServletResponse.setContentType("application/json");
          httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
          ApiError apiError = ApiError.builder()
                  .code(HttpStatus.UNAUTHORIZED.name())
                  .message(HttpStatus.UNAUTHORIZED.getReasonPhrase())
                  .httpStatus(HttpStatus.UNAUTHORIZED)
                  .build();
          httpServletResponse.getWriter().print(objectMapper.writeValueAsString(apiError));
      }
  
      private class JwtAccessDeniedHandler implements AccessDeniedHandler {
          @Override
          public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
              log.info("accessDeniedHandler", e);
              sendUnauthorized(httpServletResponse);
          }
      }
  
      private class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
          @Override
          public void commence(HttpServletRequest httpServletRequest,
                               HttpServletResponse httpServletResponse,
                               AuthenticationException e) throws IOException, ServletException {
              sendUnauthorized(httpServletResponse);
          }
      }
  }
  </code></pre>
  
  <p>Filter:</p>
  
  <pre><code>@Slf4j
  @Component
  public class JwtAuthenticationFilter extends OncePerRequestFilter {
  
      private static final String BEARER = "Bearer ";
  
      private JoseHelper joseHelper;
  
      @Autowired
      public JwtAuthenticationFilter(JoseHelper joseHelper) {
          this.joseHelper = joseHelper;
      }
  
      @Override
      protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
          String header = httpServletRequest.getHeader("Authorization");
  
          if (header == null || !header.startsWith(BEARER)) {
              log.error("JWT token is not valid");
              filterChain.doFilter(httpServletRequest, httpServletResponse);
              return;
          }
  
          final String encryptedToken = header.substring(BEARER.length());
  
          try {
              final String decryptedJwt = joseHelper.decryptJwt(encryptedToken);
              final String verifiedJwt = joseHelper.verifyJwt(decryptedJwt);
              final JwtClaims jwtClaims = joseHelper.parse(verifiedJwt);
  
              List&lt;SimpleGrantedAuthority&gt; authorities = jwtClaims.getStringListClaimValue("userRoles")
                      .stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
  
              JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwtClaims, null, authorities);
              SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
              filterChain.doFilter(httpServletRequest, httpServletResponse);
          } catch (JoseException | InvalidJwtException | MalformedClaimException e) {
              log.error("JWT token is not valid", e);
              filterChain.doFilter(httpServletRequest, httpServletResponse);
          }
      }
  
  }
  </code></pre>