简介
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;
在做跨域的时候发现好多文章里面都少了这部分,不然就算服务器同意跨域,浏览器也是不同意的。