什么是跨域?跨域解决方法
参考文章
一、为什么会出现跨域问题
出于浏览器的同源策略限制。
- 同源策略(
Sameoriginpolicy
)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。- 可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
- 同源策略会阻止一个域的
javasScript
脚本和另外一个域的内容进行交互。- 所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
二、什么是跨域
当一个请求url
的协议、域名、端口三者之间任意一个与当前页面url
不同即为跨域
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https ) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu ) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog ) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001 ) |
三、非同源限制
- 无法读取非同源网页的
Cookie
、LocalStorage
和IndexedDB
- 无法接触非同源网页的
DOM
- 无法向非同源地址发送
AJAX
请求
四、跨域解决方法
1、设置document.domain
解决无法读取非同源网页的 Cookie问题
因为浏览器是通过document.domain
属性来检查两个页面是否同源,因此只要通过设置相同的document.domain
,两个页面就可以共享Cookie
(此方案仅限主域相同,子域不同的跨域应用场景。)
// 两个页面都设置
document.domain = 'test.com';
2、跨文档通信 API:window.postMessage()
调用postMessage
方法实现父窗口http://test1.com
向子窗口http://test2.com
发消息(子窗口同样可以通过该方法发送消息给父窗口)
它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的
iframe
消息传递 - 上面三个场景的跨域数据传递
// 父窗口打开一个子窗口
var openWindow = window.open('http://test2.com', 'title');
// 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url)
openWindow.postMessage('Nice to meet you!', 'http://test2.com');
调用message事件,监听对方发送的消息
// 监听 message 消息
window.addEventListener('message', function (e) {
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // e.data 发送的消息
},false);
3、JSONP
JSONP
是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post
请求。
核心思想:网页通过添加一个<script>
元素,向服务器请求 JSON
数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
1.原生实现:
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
2. jQuery ajax
:
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
3. Vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
4.CORS
CORS
是跨域资源分享Cross-Origin Resource Sharing
的缩写。它是 W3C
标准,属于跨源 AJAX
请求的根本解决方法。
1)普通跨域请求:只需服务器端设置Access-Control-Allow-Origin
2)带cookie
跨域请求:前后端都需要进行设置
-
【前端设置】根据
xhr.withCredentials
字段判断是否带有cookie
- 原生
ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 // 前端设置是否带cookie xhr.withCredentials = true; xhr.open('post', 'http://www.domain2.com:8080/login', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } };
jQuery ajax
$.ajax({ url: 'http://www.test.com:8080/login', type: 'get', data: {}, xhrFields: { withCredentials: true // 前端设置是否带cookie }, crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie });
vue-resource
Vue.http.options.credentials = true
axios
axios.defaults.withCredentials = true
- 原生
-
【服务端设置】服务器端对于
CORS
的支持,主要是通过设置Access-Control-Allow-Origin
来进行的。如果浏览器检测到相应的设置,就可以允许Ajax
进行跨域的访问。Java
后端Nodejs
后台
var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var postData = ''; // 数据块接收中 req.addListener('data', function(chunk) { postData += chunk; }); // 数据接收完毕 req.addListener('end', function() { postData = qs.parse(postData); // 跨域后台设置 res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie 'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口) /* * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现), * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问 */ 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie }); res.write(JSON.stringify(postData)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
PHP
后台
<?php header("Access-Control-Allow-Origin:*");
Apache
需要使用mod_headers
模块来激活HTTP
头的设置,它默认是激活的。你只需要在Apache
配置文件的<Directory>
,<Location>
,<Files>
或<VirtualHost>
的配置里加入以下内容即可
Header set Access-Control-Allow-Origin *
五、后端解决跨域
1、java
过滤器过滤
@Component
public class SimpleCORSFilter implements Filter{
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
2、使用@CrossOrigin
注解
@CrossOrigin
中的2个参数:
origins
:允许可访问的域列表maxAge
:准备响应前的缓存持续的最大时间(以秒为单位)。
@CrossOrigin(maxAge = 3600)
@RequestMapping("/demo")
@RestController
public class DemoController{
@Autowired
private DemoService demoService;
@RequestMapping(method = RequestMethod.POST)
@CrossOrigin(origins = {"http://192.168.1.10:8080"})
public Demo create(@RequestBody @Validated Demo demo) {
return DemoService.create(demo);
}
}
注解@CrossOrigin
不起作用的原因:
-
springMVC
的版本要在4.2
或以上版本才支持@CrossOrigin
-
非
@CrossOrigin
没有解决跨域请求问题,而是不正确的请求导致无法得到预期的响应,导致浏览器端提示跨域问题。 -
在Controller注解上方添加
@CrossOrigin
注解后,仍然出现跨域问题:在
@RequestMapping
注解中没有指定Get
、Post
方式@RequestMapping(method = RequestMethod.GET)
3、后台配置同源Cors
(推荐)
在SpringBoot2.0
上的跨域 用以下代码配置 即可完美解决前后端跨域请求问题(推荐)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 实现基本的跨域请求
* @author linhongcun
*/
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
/*是否允许请求带有验证信息*/
corsConfiguration.setAllowCredentials(true);
/*允许访问的客户端域名*/
corsConfiguration.addAllowedOrigin("*");
/*允许服务端访问的客户端请求头*/
corsConfiguration.addAllowedHeader("*");
/*允许访问的方法名,GET POST等*/
corsConfiguration.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
基于WebMvcConfigurerAdapter
配置加入Cors
的跨域
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}
4、使用SpringCloud
网关
服务网关(zuul
)又称路由中心,用来统一访问所有api
接口,维护服务。
Spring Cloud Zuul
通过与Spring Cloud Eureka
的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样去指定具体的服务实例地址,只需要通过Ant
模式配置文件参数即可
5、使用nginx
做转发
现在有两个网站想互相访问接口 在http://a.a.com:81/A
中想访问 http://b.b.com:81/B
那么进行如下配置即可,然后通过访问 www.my.com/A
里面即可访问 www.my.com/B
server {
listen 80;
server_name www.my.com;
location /A {
proxy_pass http://a.a.com:81/A;
index index.html index.htm;
}
location /B {
proxy_pass http://b.b.com:81/B;
index index.html index.htm;
}
}
如果是两个端口想互相访问接口 在http://b.b.com:80/Api
中想访问 http://b.b.com:81/Api
那么进行如下配置即可,使用nginx
转发机制就可以完成跨域问题
server {
listen 80;
server_name b.b.com;
location /Api {
proxy_pass http://b.b.com:81/Api;
index index.html index.htm;
}
}