跳到主要内容

33、SpringMVC源码分析 - 请求出错时浏览器误报跨域问题

前言

项目中定义了跨域过滤器,有些接口请求时出现错误,浏览器上却显示了跨域错误。

一、现象

Access to XMLHttpRequest at 'http://192.168.100.73:7901/demo/ftpFile/upload?project=11&projectVersion=11&relatedSoftware=CppUnit%E8%A2%AB%E6%B5%8B%E4%BB%B6&unit=11&type=document' from origin 'http://192.168.100.73:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
POST http://192.168.100.73:7901/demo/ftpFile/upload?project=11&projectVersion=11&relatedSoftware=CppUnit%E8%A2%AB%E6%B5%8B%E4%BB%B6&unit=11&type=document net::ERR_FAILED

二、跨域过滤器

public class CustomCorsFilter extends CorsFilter {
   
     
    private CorsProps corsProps;

    public CustomCrosFilter(CorsConfigurationSource configSource, CorsProps corsProps) {
   
     
        super(configSource);
        this.corsProps = corsProps;

    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
   
     
        response.setHeader("Access-Control-Allow-Origin", corsProps.getOrigin());
        response.setHeader("Access-Control-Allow-Credentials", corsProps.getCredentials());
        response.setHeader("Access-Control-Allow-Methods", corsProps.getMethods());
        response.setHeader("Access-Control-Allow-Headers", corsProps.getHeaders());
        filterChain.doFilter(request, response);
    }

三、分析

1、 通过调试,请求经过跨域过滤器时,响应头中设置了跨域的信息;

2、 请求出现异常时,异常抛给了web容器,这里使用的容器是Undertow,在这里响应头被清理掉了;

//重置 response
response.reset();                       //reset the response
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
//清理了响应头
exchange.getResponseHeaders().clear();
//获取新的请求错误路径
String location = servletContext.getDeployment().getErrorPages().getErrorLocation(t);

3、 在找到对应错误页面或默认错误页面后,没有再次通过跨域过滤器设置跨域信息,导致形响应结果中没有带跨域信息,引起浏览器报跨域的错;

四、解决方式

1、 在错误页面中设置跨域信息;

每个方法都调用一下设置跨域信息的方法

@RestController
public class ErrorPageController {
   
     

    @Autowired
    private CorsProps corsProps;

    @RequestMapping(value = "/400", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity to400() {
   
     
        setCors();
        return new ResponseEntity(400, "请求有误");
    }

    @RequestMapping(value = "/404", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity to404() {
   
     
        setCors();
        return new ResponseEntity(404, "找不到资源");
    }

    @RequestMapping(value = "/408", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    @ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
    public ResponseEntity to408() {
   
     
        setCors();
        return new ResponseEntity(408, "请求超时");
    }

    @RequestMapping(value = "/500", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity to500() {
   
     
        setCors();
        return new ResponseEntity(500, "服务器错误");
    }

    public void setCors() {
   
     
        HttpServletResponse response = SpringUtils.getResponse();
        String origin = SpringUtils.getRequest().getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin);
        //服务器同意客户端发送cookies
        response.setHeader("Access-Control-Allow-Credentials", corsProps.getCredentials());

        response.setHeader("Access-Control-Allow-Methods", corsProps.getMethods());
        response.setHeader("Access-Control-Allow-Headers", corsProps.getHeaders());
    }
}

这样错误信息就变成了正常的信息:

POST http://192.168.100.73:7901/demo/ftpFile/unitSearch 400 (Bad Request)
Uncaught (in promise) {
   
     status: 400, message: "请求有误", desc: null, stackTrace: null, value: null, …}

2、 在切面中设置跨域信息;

统一设置跨域信息

@Aspect
@Component
public class CorsAspect {
   
     

    @Autowired
    private CrosProps corsProps;

    /**
     * 让切面拦截到 ErrorPageController
     */
    @Pointcut("execution(* com.iscas.biz.config..error..*.*(..))")
    public void errorMethodPointcut() {
   
     
    }

    @Around(value = "errorMethodPointcut()")
    public Object around(final ProceedingJoinPoint joinPoint) throws Throwable {
   
     

        HttpServletResponse response = SpringUtils.getResponse();
        String origin = SpringUtils.getRequest().getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin);
        //服务器同意客户端发送cookies
        response.setHeader("Access-Control-Allow-Credentials", corsProps.getCredentials());

        response.setHeader("Access-Control-Allow-Methods", corsProps.getMethods());
        response.setHeader("Access-Control-Allow-Headers", corsProps.getHeaders());

        Object result = joinPoint.proceed();
        return result;
    }
}

3、 在请求错误页面ErrorPageController时通过跨域过滤器;

在跨域过滤器中重写 OncePerRequestFilter 的 shouldNotFilterErrorDispatch( )方法,让错误请求进入跨域过滤器设置跨域信息。

/**
     * 错误请求,是否不进入该过滤器
     * 默认为true,错误请求不会进入该过滤器,修改为false
     */
    protected boolean shouldNotFilterErrorDispatch() {
   
     
        return false;
    }