跨域原因:
- 浏览器限制,浏览器出于安全原因,当它发现你的请求跨域了,它就会开启校验,如果校验不通过就会报跨域安全错误;
- 请求域名、协议、端口不一致,产生跨域;
- 发出的是XHR(XMLHttpRequest)请求;
满足上面三点浏览器就会产生跨域报错;
跨域解决办法:
- 针对浏览器限制,可以关闭浏览器的跨域校验,这个方法实际环境不能实现,不可能要求每个用户关闭浏览器跨域校验(不推荐);
- 针对XHR请求问题,可以使用Jsonp, Jsonp需要修改前后端代码,使用起来并不方便,现在使用的比较少(不推荐);
- 被调用方解决(服务器端)处理,返回校验信息,告诉浏览器服务器允许客户端进行跨域请求,那么浏览器通过校验就不会限制跨域,这个方式需要服务器端是本公司的,修改服务器端支持跨域(推荐);
- 调用方解决,服务器不是本公司,或者没有办法修改服务器端代码。那么需要调用方通过代理等方式隐藏跨域(推荐);
被调用方解决(服务器端)
- 应用服务器端
通过Filter来处理
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @description 跨域处理的Filter
* @author: fanxl
* @date: 2018/10/10 0010 12:42
*/
public class CrosFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 如果是所有方法和所有域名 可以使用 *
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
response.addHeader("Access-Control-Allow-Methods", "GET");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
注册Filter
@Bean
public FilterRegistrationBean registrationFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.addUrlPatterns("/*");
bean.setFilter(new CrosFilter());
return bean;
}
对于简单请求,浏览器是先请求再做跨域校验,而对于非简单请求,则是先判断再请求。
工作中简单请求:
请求方法为: GET HEAD POST
请求header里面:
无自定义头
Content-Type为以下几种:
text/plain
multipart/form-data
application/x-www-form-urlencoded
非简单请求:
请求方法为: PUT DELETE
请求header里面有自定义头
Content-Type 为application/json的
对于非简单请求,浏览器会先发一个预检命令,预检命令通过之后再发生请求。这个时候需要增加
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
由于非简单请求每次都用发送两遍请求,所以可以设置预检命令请求缓存,这里指定缓存时间 单位秒
response.addHeader("Access-Control-Max-Age", "3600");
带Cookie的跨域
如果请求是带Cookie的,那么服务器端设置的域名不能设置为*,必须设置具体的域名,而如果设置具体的域名,那么就不能实现对其他域名的跨域支持,这个时候就需要动态是设置跨域域名,根据请求的域名进行配置,代码如下:
HttpServletRequest request = (HttpServletRequest) servletRequest;
String origin = request.getHeader("Origin");
if (!StringUtils.isEmpty(origin)){
response.addHeader("Access-Control-Allow-Origin", origin);
}
// 支持cookie
response.addHeader("Access-Control-Allow-Credentials", "true");
支持自定义头的跨域
// 支持所有自定义头的跨域
String heads = request.getHeader("Access-Control-Allow-Headers");
if (!StringUtils.isEmpty(heads)) {
response.addHeader("Access-Control-Allow-Headers", heads);
}
- Spring服务器
直接在对应Controller上添加@CrossOrigin即可
@RestController
@RequestMapping(value = "/api/v1/tickey")
@CrossOrigin
public class TickeyRestController
- 服务器端(nginx配置)
# nginx配置支持跨域
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Headers $http_access_control_request_headers;
if ($request_method = OPTIONS) {
return 200;
}
调用方解决(隐藏跨域)
调用方解决方案,是利用调用方的Nginx服务器的反向代理来实现