简介

CORS:全称"跨域资源共享"(Cross-origin resource sharing)。

  • CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS,IE则不能低于IE10。CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax请求并无差异。so,实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。
  • Spring解决跨域问题其实很简单,可以在局部设置,也可以抽出一个配置类,我这里做的是整体,下面的代码直接粘过去就行不用改动。
@Configuration
public class CORSConfiguration {
    @Bean
    public WebMvcConfigurer CORSConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                      .allowedOrigins("*")
                        .allowedMethods("*")
                        .allowedHeaders("*")
                        //.allowedMethods("GET", "POST", "DELETE", "PUT")
                        //设置是否允许跨域传cookie
                        .allowCredentials(true)
                        //设置缓存时间,减少重复响应
                        .maxAge(3600);
            }
        };
    }
}

其实这里写完就可以进行跨域请求了,但是浏览器这个坑爹的东西,跨域问题后面又跟着简单和复杂请求,下面先解释一下这两种请求:

  • 跨域请求又分为简单请求与复杂请求(这是下面解决问题的重点):
  • 1、 同时满足以下两种条件的,就是简单请求

(1) 请求方法是以下三种方法之一:

  • HEAD、 GET、POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept、 Accept-Language、Content-Language、Last-Event-ID、
    Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

2、上述只要有一条不满足就为复杂请求。

  • 在复杂请求中,有个地方是需要我们注意的:options预检请求,简单来说就是浏览器在发送复杂请求时的一种处理方式,在真正发送请求时候,会先发送一个预检请求,用来确定服务器响应是否正确,是否能接收真正的请求,如果options请求过后状态是500,那真正请求也就不在发送了。
  • 根据上面说的我们知道,options请求只是去检查,并不是真正的请求,也就不会携带数据,那么我们想要接收真正请求就需要将它放行,过滤掉。我这里权限用的是shiro,过滤器的写法都大同小异,后面会出一个springboot整合shiro的文章。
  • 下面代码直接复制进去就可以了。
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class ShiroUserFilter extends UserFilter {


    /*在访问过来的时候检测是否为OPTIONS请求,如果是就直接返回true*/
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            setHeader(httpRequest,httpResponse);
            return true;
        }
        return super.preHandle(request,response);
    }

     /* 该方法会在验证失败后调用,因此重写改成传输JSON数据*/
    @Override
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        setHeader((HttpServletRequest) request,(HttpServletResponse) response);
        PrintWriter out = response.getWriter();
        //out.println(JSONObject.toJSONString(ResultUtil.error(ExceptionEnum.IS_NOT_LOGIN)));
        out.flush();
        out.close();
    }

 /* 实现跨域*/
    private void setHeader(HttpServletRequest request,HttpServletResponse response){
        //跨域的header设置
        response.setHeader("Access-control-Allow-Origin", "*");
        response.setHeader("Cache-Control","no-cache");
        response.setHeader("Access-Control-Allow-Headers",  "Origin, X-Requested-With, Content-Type, Accept");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET,PUT,OPTIONS, DELETE");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        //防止乱码,适用于传输JSON数据
        response.setHeader("Content-Type","application/json;charset=UTF-8");
        response.setStatus(HttpStatus.OK.value());
    }

    @Bean
    public FilterRegistrationBean registration(ShiroUserFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }

}

到这里整个的后端跨域也就都完成了。但是我在做这部分的时候,可以进行跨域请求,http请求状态为200,但是前端无法接收到参数,这里面有个小细节ajax在进行请求时候,由于浏览器的同源问题,默认只能收到同一域名下的cookie,所以出现了状态200,但是无数据的情况。
只需要在前端代码中加入下面的代码

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

在做跨域的时候发现好多文章里面都少了这部分,不然就算服务器同意跨域,浏览器也是不同意的。