Tomcat环境下JavaEE Servlets的CORS跨域POST请求问题排查
问题场景
已安装Access Control插件但无效果,使用Servlets结合Gson开发接口。通过Hoppscotch调用所有HTTP方法均正常,Axios发起的GET请求也能成功,但Vue项目中用Axios发送POST请求时触发CORS错误:
Access to XMLHttpRequest at 'http://localhost:8080/demo5_war_exploded/client'
from origin 'http://localhost:8081' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
同时伴随请求失败错误:
xhr.js:233
POST http://localhost:8080/demo5_war_exploded/client net::ERR_FAILED
已在doGet和doPost方法中设置CORS响应头,但POST请求仍无法解决问题,相关代码如下:
异常的doPost代码
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); response.setHeader("Access-Control-Max-Age", "86400"); response.setHeader("Content-Type", "application/json"); JsonReader jsonReader = Json.createReader(request.getReader()); JsonObject jsonObject = jsonReader.readObject(); jsonReader.close(); // extract the fields from the JSON object String name = jsonObject.getString("name"); String email = jsonObject.getString("email"); // create the Client object Client client = new Client(name, email); // insert the client into the database try { clientDao.insert(client); response.setStatus(HttpServletResponse.SC_CREATED); } catch (SQLException e) { e.printStackTrace(); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } }
正常工作的doGet代码
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); response.setHeader("Access-Control-Max-Age", "86400"); response.setHeader("Content-Type", "application/json"); String idString = request.getParameter("id"); if (idString != null) { // get the client with the specified id int id = Integer.parseInt(idString); try { Client client = clientDao.buscarPorId(id); if (client != null) { JsonObjectBuilder builder = Json.createObjectBuilder(); builder.add("name", client.getName()); builder.add("email", client.getEmail()); JsonObject jsonObject = builder.build(); response.setContentType("application/json"); PrintWriter out = response.getWriter(); out.print(jsonObject); out.flush(); } else { response.setStatus(HttpServletResponse.SC_NOT_FOUND); } } catch (SQLException e) { e.printStackTrace(); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } else { // get all the client try { // a different way to get the json and simpler TBH List<Client> client = clientDao.list(); Gson gson = new Gson(); String clientJSON = gson.toJson(client); PrintWriter printWriter = response.getWriter(); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); printWriter.write(clientJSON); printWriter.close(); } catch (SQLException e) { e.printStackTrace(); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } }
问题根源
核心问题在于浏览器的预检请求(OPTIONS)未被处理:
当前端发送带Content-Type: application/json的POST请求时,浏览器会自动先发送OPTIONS请求,验证服务器是否允许该跨域操作。你的Servlet没有实现OPTIONS方法的处理逻辑,导致服务器返回的响应中缺失CORS头,浏览器因此拦截了后续的正式POST请求。而GET请求属于简单请求,不需要触发预检,所以能正常工作。
解决方案
方案1:在Servlet中添加OPTIONS方法处理
直接在当前Servlet中重写doOptions方法,返回符合要求的CORS响应头:
@Override protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws IOException { // 设置跨域允许规则 response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); response.setHeader("Access-Control-Max-Age", "86400"); // 预检请求无需返回实体内容,直接返回200状态码 response.setStatus(HttpServletResponse.SC_OK); }
方案2:使用Filter统一处理CORS(推荐)
如果存在多个需要跨域的Servlet,使用Filter统一拦截所有请求并处理CORS,避免重复代码:
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter("/*") // 拦截所有请求路径 public class CORSFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; // 设置跨域规则 httpResponse.setHeader("Access-Control-Allow-Origin", "*"); httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); httpResponse.setHeader("Access-Control-Max-Age", "86400"); // 处理OPTIONS预检请求,直接返回200,无需执行后续逻辑 if ("OPTIONS".equalsIgnoreCase(((javax.servlet.http.HttpServletRequest) request).getMethod())) { httpResponse.setStatus(HttpServletResponse.SC_OK); return; } chain.doFilter(request, response); } @Override public void destroy() {} }
注:如果项目支持Servlet 3.0+,@WebFilter注解会自动注册Filter,无需手动在web.xml中配置。
注意事项
- 浏览器插件仅适用于本地开发测试,生产环境必须通过服务器端配置CORS规则。
- 生产环境中
Access-Control-Allow-Origin建议指定具体的前端域名(如http://localhost:8081),而不是使用通配符*,提升安全性。
内容的提问来源于stack exchange,提问作者Raúl Peñate

