目录
一、同源策略(以下介绍来自百度百科)
简单理解就是两个URL的协议、域名、端口号都相同,就称这两个URL同源。否则就是跨域
二、什么是跨域(CORS全称Cross-Origin Resource Sharing,意为跨域资源共享)
广义的跨域:
1.) 资源跳转: A链接、重定向、表单提交。
2.) 资源嵌入:dom标签中的链接、图片、脚本,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
主要因为上文描述的同源策略限制了 以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
所以可以理解同源策略是产生跨域的问题的罪魁祸首:)
三、请求的分类(简单请求和复杂请求)
1、简单请求
简单请求必须要同时满足下面三个条件:
1)请求方式只能是:GET、POST、HEAD
2)HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
3)Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
2、复杂请求
不满足简单请求的条件,那么就是复杂请求。
复杂请求会在正式请求发送之前,先发一个预检请求进行校验,校验通过后才能进行正式请求。
例如:
浏览器现在要发送一个put的复杂请求,那么在put请求发送之前,浏览器先发送一个options请求。
options请求头信息(会隐藏一些具体头信息, 只有正常访问时才显示):
OPTIONS /cors HTTP/1.1
Origin: localhost:3000
Access-Control-Request-Method: PUT // 表示使用的什么HTTP请求方法
Access-Control-Request-Headers: X-Custom-Header // 表示浏览器发送的自定义字段
Host: localhost:3000
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0...
服务器收到options请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应
options响应头信息
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:3000 // 表示http://localhost:3000可以访问数据
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
当options请求通过之后发出正式的HTTP请求,倘若options请求不通过,则服务器不允许此次访问,从而抛出错误
options请求通过之后的,浏览器发出发请求
PUT /cors HTTP/1.1
Origin: http://api.zhenai.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
options请求缓存
在options请求中,我们可以通过设置响应头的参数Access-Control-Max-Age来对结果进行缓存
比如: Access-Control-Max-Age: 600 表示对options检验结果进行十分钟的缓存
url变化会导致缓存失效,需要重新验证options请求的返回值
预检不关心post data
header变化,如果是去掉了自定义的header使得请求变成简单请求,不会发送options请求。如果是增加其他的header,是会重新验证Access-Control-Allow-Headers的值。
cookie变化,只要后端允许发送cookie,cookie值变化不会导致缓存失效。
四、跨域的解决方式(个人自己试过前三种)
1、通过jsonp跨域(注意注意 这种方式只对get请求有效)
1)原生JS实现:
<script>
var script = document.createElement('script');
script.type = 'text/javascript'; // 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
} </script>
2)jquery ajax:
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
3)vue.js:
this.$http.jsonp('http://www.domain2.com:8080/login',
{
params: {},
jsonp: 'onBack'
}
).then((res) => {
console.log(res);
})
2、nginx代理跨域
1) nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *;
}
2、 nginx反向代理接口跨域
跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
nginx具体配置:
#proxy服务器
server {
listen xxxx;
server_name www.xxxx.com;
location / {
proxy_pass http://localhost:3001;
#指定允许跨域的方法,*代表所有
add_header Access-Control-Allow-Methods *;
#预检命令的缓存,如果不缓存每次会发送两次请求
add_header Access-Control-Max-Age 3600;
#带cookie请求需要加上这个字段,并设置为true
add_header Access-Control-Allow-Credentials true;
#表示允许这个域跨域调用(客户端发送请求的域名和端口)
#$http_origin动态获取请求客户端请求的域 不用*的原因是带cookie的请求不支持*号
add_header Access-Control-Allow-Origin $http_origin;
#表示请求头的字段 动态获取
add_header Access-Control-Allow-Headers
$http_access_control_request_headers;
#OPTIONS预检命令,预检命令通过时才发送请求
#检查请求的类型是不是预检命令
if ($request_method = OPTIONS){
return 200;
}
}
}
3、jquery的ajax和java后台
jquery
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
java后端代码在controller请求中加入如下代码
// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "*");
//* 代办允许所有方法
response.setHeader("Access-Control-Allow-Methods", "*");
// Access-Control-Max-Age 用于 CORS 相关配置的缓存
response.setHeader("Access-Control-Max-Age", "3600");
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "*");
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true");
4、WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');// 连接成功处理socket.on('connect', function() { // 监听服务端消息
socket.on('message', function(msg) { console.log('data from server: ---> ' + msg);
}); // 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
</script>
5、vue proxyTable
其实,在我们主流使用的MVVM框架中,配置项里面也提供解决跨域问题的能力,继续举个 ,以vue2.x为例,我们可以通过在config/index.js中添加配置项实现跨域请求:
proxyTable: {
'/apis': {
// 测试环境
target: 'http://www.zhenai.com/', // 接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/apis': '' //需要rewrite重写的,
}
}
}
6、springboot的解决方式
1)在目标方法中加@CrossOrigin注解
@GetMapping("/list")
@CrossOrigin
public List<String> list(){
List<String> list = Arrays.asList("Java");
return list;
}
2)添加一个CORS的过滤器
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}
3)第三种:实现 WebMvcConfigurer 接口,重写 addCorsMappings 方法
如果同时使用了spring security需要在security的配置文件中修改,否则不生效
@Configuration
public class CorsConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")//允许哪些域访问
.allowedMethods("GET","POST","PUT","DELETE","HEAD","OPTIONS")//允许哪些方法访问
.allowCredentials(true)//是否允许携带cookie
.maxAge(3600)//设置浏览器询问的有效期
.allowedHeaders("*");//
}
}
}
spring security情况下
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/hello1").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
// 跨域配置
.and()
.cors()
.configurationSource(configurationSource());
}
CorsConfigurationSource configurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}