框架:springboot+IDEA+spring security oauth2
背景:用户登陆获取token,用户名或密码验证失败时,执行onAuthenticationFailure方法,已经正常返回错误码和错误信息,但控制台仍然抛出异常,错误信息如下。
2021-09-12 07:16:25.490 WARN 964 --- [nio-8090-exec-1] d.c.h.CustomAuthenticationFailureHandler : 认证失败,返回报文为:{"code":1100,"info":"用户不存在."}
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]
错误码和错误信息已正常返回,但控制台仍然报错,不是我想要的结果,那咋办?没办法,只有继续改造,分分种让他学会做人,方显程序猿本色。哦,对了,自定义异常处理代码是炸个样子的。
@Component("CustomAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.reset();//重置response,否则报错(getWriter() has already been called for this response)
// PrintWriter printWriter = httpServletResponse.getWriter();不能使用getWrite(),不能正确返回错误
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("认证失败,返回报文为:"+rspBodyStr);
out.write(rspBodyStr.getBytes(StandardCharsets.UTF_8));//字符串写入输出流
out.close();
super.onAuthenticationFailure(request,response,exception);
}
}
Cannot call sendError() after the response has been committed,中文含义为:response被提交后不能调用sendError()方法。
分析:根据错误日志,初步判断应该是重复提交或重复返回啦,但此处涉及到的只有response,所以只能去父类(onAuthenticationFailure)去找。打开一看,代码如下。
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);
}
}
}
很显然,无论defaultFailureUrl为null时,又执行sendError()方法,应该是与之前设置的重复,而错误日志中也是此方法报错,既然已经返回我想要的错误码和错误信息,干脆不调父类方法。一试,果不其然,控制台不再报错,亦正常返回。
解决方案:不再调用父类方法,注释super.onAuthenticationFailure(request,response,exception);语句。
ok,搞定,开始分享成功的喜悦。