[Solved] cannot call sendError() after the response has been committed

framework: springboot+IDEA+spring security oauth2
background: user login to get token, username or password authentication failure, the execution of onAuthenticationFailure method, has returned the normal error code and error message, but the console still throws an exception, the error message is as follows.

2021-09-12 07:16:25.490  WARN 964 — [nio-8090-exec-1] d.c.h.CustomAuthenticationFailureHandler : Authentication failed, the return message is: {“code”:1100, “info”: “User does not exist.”}
2021-09-12 07:16:25.500 ERROR 964 — [nio-8090-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/api/sso] threw exceptionjava.lang.IllegalStateException: Cannot call sendError() after the response has been committed
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:456) ~[tomcat-embed-core-9.0.52.jar:9.0.52]
at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:120) ~[tomcat-embed-core-9.0.52.jar:4.0.FR]
at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:120) ~[tomcat-embed-core-9.0.52.jar:4.0.FR]
at org.springframework.security.web.util.OnCommittedResponseWrapper.sendError(OnCommittedResponseWrapper.java:126) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure(SimpleUrlAuthenticationFailureHandler.java:86) ~[spring-security-web-5.5.2.jar:5.5.2]
at com.datong.liran.datongssoserver.config.handle.CustomAuthenticationFailureHandler.onAuthenticationFailure(CustomAuthenticationFailureHandler.java:39) ~[classes/:na]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication(AbstractAuthenticationProcessingFilter.java:342) ~[spring-security-web-5.5.2.jar:5.5.2]

The error code and error message have been returned normally, but the console still reports an error, not the result I want, so what should I do? The custom exception handling code looks like this.

@Component("CustomAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.reset();
//        PrintWriter printWriter = httpServletResponse.getWriter();
        OutputStream out = response.getOutputStream();
        response.setContentType("application/json;charset=UTF-8");
        Result result = Result.error(1100,exception.getMessage(),null);
        String rspBodyStr = JSONObject.toJSONString(result);
        logger.warn("The authentication fails and the return message is:"+rspBodyStr);
        out.write(rspBodyStr.getBytes(StandardCharsets.UTF_8));
        out.close();
        super.onAuthenticationFailure(request,response,exception);
    }
}

Cannot call senderror() after the response has been committed.

Analysis: according to the error log, it is preliminarily judged that it should be repeated submission or repeated return, but only response is involved here, so you can only go to the parent class (onauthenticationfailure). Open it and have a look. The code is as follows.

public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        if (this.defaultFailureUrl == null) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Sending 401 Unauthorized error since no failure URL is set");
            } else {
                this.logger.debug("Sending 401 Unauthorized error");
            }

            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        } else {
            this.saveException(request, exception);
            if (this.forwardToDestination) {
                this.logger.debug("Forwarding to " + this.defaultFailureUrl);
                request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
            } else {
                this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
            }

        }
    }

Obviously, when the defaultfailureurl is null, the senderror () method is executed, which should be the same as the previous setting, and this method also reports an error in the error log. Since the error code and error information I want have been returned, I simply do not call the parent method. After a try, sure enough, the console no longer reports an error and returns normally.

Solution: no longer call the parent class method, and annotate super.onauthenticationfailure (request, response, exception); sentence.

OK, get it done and start sharing the joy of success.

Read More: