赞
踩
目录
(4)JSONP的流程(以第三方API地址为例,不必考虑后台程序)
6、设置document.domain解决无法读取非同源网页的 Cookie问题
7、跨文档通信 API:window.postMessage()
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
广义的跨域:
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: link script img frame等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
特别说明两点:
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
前端部分其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
出于浏览器的同源策略限制。同源策略/SOP(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
但是有三个标签是允许跨域加载资源:
1.<img src=XXX> 2.<link href=XXX> 3.<script src=XXX>
当前页面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) |
http://www.domain.com/a.js | http://192.168.4.12/b.js | 跨域 | 域名和域名对应相同IP,也非同源 |
但所有的跨域都必须经过信息提供方的允许。如果未经允许即可获取,那是浏览器同源策略出现漏洞。
利用 <script>
元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据。服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。JSONP请求一定需要对方的服务器做支持才可以。
JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求), Jquery中ajax的核心是通过 XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的 js脚本。当我们正常地请求一个JSON数据的时候,服务端返回的是一串JSON类型的数据,而我们使用 JSONP模式来请求数据的时候服务端返回的是一段可执行的JavaScript代码。
JSONP优点是兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性。
声明一个回调函数,其函数名(如fn)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
创建一个<script>
标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=fn)。
服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是fn,它准备好的数据是fn([{"name":"jianshu"}])。
最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(fn),对返回的数据进行操作。
- <script type="text/javascript" src="http://test.com/jsonServerResponse?callback=fn"></script>
- //向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
-
-
- //处理服务器返回回调函数的数据
- <script type="text/javascript">
- function fn(data) {
- alert(data.msg);
- }
- </script>
其中 fn 是客户端注册的回调的函数,目的获取跨域服务器上的json数据后,对数据进行在处理
最后服务器返回给客户端数据的格式为:
fn({ msg:'this is json data'})
JSONP都是GET和异步请求的,不存在其他的请求方式和同步请求,且jQuery默认就会给JSONP的请求清除缓存。
- $.ajax({
- url:"http://crossdomain.com/jsonServerResponse",
-
- dataType:"jsonp", //数据类型为jsonp
-
- type:"get",//可以省略
-
- jsonpCallback:"fn",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
-
- jsonp:"jsonp",//->把传递函数名的那个形参callback变为jsonp,可省略
-
- success:function (data){
- console.log(data);
- }
- error : function() {
- alert('fail');
- }
- });
用法:
①dataType改为jsonp
②jsonp : "jsonp"—发送到后端实际为http://crossdomain.com/jsonServerResponse?jsonp=jQueryxxx ,可以不修改
③后端获取get请求中的jsonp
④构造回调结构
- //后端
- String jsonpCallback = request.getParameter("jsonp");
-
- //构造回调函数格式jsonp(数据)
- resp.getWriter().println(jsonp+"("+jsonObject.toJSONString()+")");
CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。
优点在于功能更加强大支持各种HTTP Method,缺点是兼容性不如JSONP。
a、前端代码(需要判断浏览器是否支持情况)
- function createCORSRequest(method, url) {
- var xhr = new XMLHttpRequest();
- if ("withCredentials" in xhr) {
-
- // 此时即支持CORS的情况
- // 检查XMLHttpRequest对象是否有“withCredentials”属性
- // “withCredentials”仅存在于XMLHTTPRequest2对象里
- xhr.open(method, url, true);
-
- } else if (typeof!= "undefined") {
-
- // 否则检查是否支持XDomainRequest,IE8和IE9支持
- // XDomainRequest仅存在于IE中,是IE用于支持CORS请求的方式
- xhr = new XDomainRequest();
- xhr.open(method, url);
-
- } else {
-
- // 否则,浏览器不支持CORS
- xhr = null;
-
- }
- return xhr;
- }
-
- var xhr = createCORSRequest('GET', url);
- if (!xhr) {
- throw new Error('CORS not supported');
- }
b、服务器端
- response.addHeader(‘Access-Control-Allow-Origin:*’);//允许所有来源访问
- response.addHeader(‘Access-Control-Allow-Method:POST,GET’);//允许访问的方式
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生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>
-
- //Nodejs socket后台:
- var http = require('http');
- var socket = require('socket.io');
- // 启http服务
- var server = http.createServer(function(req, res) {
- res.writeHead(200, {
- 'Content-type': 'text/html'
- });
- res.end();
- });
- server.listen('8080');
- console.log('Server is running at port 8080...');
- // 监听socket连接
- socket.listen(server).on('connection', function(client) {
- // 接收信息
- client.on('message', function(msg) {
- client.send('hello:' + msg);
- console.log('data from client: ---> ' + msg);
- });
- // 断开处理
- client.on('disconnect', function() {
- console.log('Client socket has closed.');
- });
- });
实现原理很简单,若想在B站点中通过Ajax访问A站点获取结果,固然有ajax跨域问题,但在B站点中访问B站点获取结果,不存在跨域问题,这种方式实际上是在B站点中ajax请求访问B站点的HttpClient,再通过HttpClient转发请求获取A站点的数据结果。但这种方式产生了两次请求,效率低,但内部请求,抓包工具无法分析,安全。
- $.ajax({
- type : "GET",
- async : false,
- url : "http://b.b.com:8080/B/FromAjaxservlet?userName=644064",
- dataType : "json",
- success : function(data) {
- alert(data["userName"]);
- },
- error : function() {
- alert('fail');
- }
- });
- @WebServlet("/FromAjaxservlet")
- public class FromAjaxservlet extends HttpServlet{
-
-
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- try {
- //创建默认连接
- CloseableHttpClient httpClient = HttpClients.createDefault();
- //创建HttpGet对象,处理get请求,转发到A站点
- HttpGet httpGet = new HttpGet("http://a.a.com:8080/A/FromServlet?userName="+req.getParameter("userName"));
- //执行
- CloseableHttpResponse response = httpClient.execute(httpGet);
- int code = response.getStatusLine().getStatusCode();
- //获取状态
- System.out.println("http请求结果为:"+code);
- if(code == 200){
- //获取A站点返回的结果
- String result = EntityUtils.toString(response.getEntity());
- System.out.println(result);
- //把结果返回给B站点
- resp.getWriter().print(result);
- }
- response.close();
- httpClient.close();
- } catch (Exception e) {
- }
- }
- }
www.a.a.com不能直接请求www.b.b.com的内容,可以通过nginx,根据同域名,但项目名不同进行区分。什么意思呢?这么说可能有点抽象。假设我们公司域名叫www.nginxtest.com
当我们需要访问www.a.a.com通过www.nginxtest.com/A访问,并通过nginx转发到www.a.a.com
当我们需要访问www.b.b.com通过www.nginxtest.com/B访问,并通过nginx转发到www.a.a.com
我们访问公司的域名时,是"同源"的,只是项目名不同,此时项目名的作用只是为了区分,方便转发。如果你还不理解的话,先看看是怎么进行配置的:
- server {
- listen 80;
- server_name www.nginxtest.com;
- location /A {
- proxy_pass http://a.a.com:81;
- index index.html index.htm;
- }
- location /B {
- proxy_pass http://b.b.com:81;
- index index.html index.htm;
- }
- }
我们访问以www.nginxtest.com开头且端口为80的网址,nginx将会进行拦截匹配,若项目名为A,则分发到a.a.com:81。实际上就是通过"同源"的域名,不同的项目名进行区分,通过nginx拦截匹配,转发到对应的网址。整个过程,两次请求,第一次请求nginx服务器,第二次nginx服务器通过拦截匹配分发到对应的网址。
因为浏览器是通过document.domain属性来检查两个页面是否同源,因此只要通过设置相同的document.domain,两个页面就可以共享Cookie
- // 两个页面都设置
- document.domain = 'test.com';
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。
调用postMessage方法实现父窗口http://test1.com向子窗口http://test2.com发消息(子窗口同样可以通过该方法发送消息给父窗口)
- // 父窗口打开一个子窗口
- 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);
想在本地调另一台服务器上的接口,但是一直报跨域的错误,错误如下:
ajax:其中的dftApiContextFile是配置文件中的该接口的路径
最后是以下方式解决的,在这里记录一下:
1、将要调的接口的包和自己的项目在本地启动
2、配置Nginx
(1)将Nginx解压
(2)修改nginx.conf(这里给一个示例,主要是修改为自己的端口号)
nginx通过proxy_pass_http 配置代理站点,upstream实现负载均衡。
- #user nobody;
- worker_processes 1;
-
- #error_log logs/error.log;
- #error_log logs/error.log notice;
- #error_log logs/error.log info;
-
- #pid logs/nginx.pid;
-
-
- events {
- worker_connections 1024;
- }
-
-
- http {
- include mime.types;
- default_type application/octet-stream;
-
- #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
- # '$status $body_bytes_sent "$http_referer" '
- # '"$http_user_agent" "$http_x_forwarded_for"';
-
- #access_log logs/access.log main;
-
- sendfile on;
- #tcp_nopush on;
-
- #keepalive_timeout 0;
- keepalive_timeout 65;
-
- #gzip on;
- upstream gxweb_pool {
- ip_hash;
- server 127.0.0.1:8089;
- }
- upstream dataweb_pool {
- ip_hash;
- #server 127.0.0.1:8081;
- server 127.0.0.1:9091;
- }
- upstream resource_pool {
- ip_hash;
- #server 127.0.0.1:8081;
- server 127.0.0.1:8085;
- }
-
- server {
- listen 80;
- server_name localhost;
-
- #charset koi8-r;
-
- #access_log logs/host.access.log main;
-
- location / {
- root html;
- index index.html index.htm;
- }
-
- location ^~ /apply {
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header REMOTE-HOST $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_pass http://gxweb_pool/apply;
- proxy_redirect off;
- }
-
- location ^~ /portal {
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header REMOTE-HOST $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_pass http://dataweb_pool/portal;
- proxy_redirect off;
- }
-
- location ^~ /dataweb{
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header REMOTE-HOST $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_pass http://dataweb_pool/portal;
- proxy_redirect off;
- }
-
- location ^~ /resource{
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header REMOTE-HOST $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_pass http://resource_pool/resource;
- proxy_redirect off;
- }
-
-
-
- #error_page 404 /404.html;
-
- # redirect server error pages to the static page /50x.html
- #
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- root html;
- }
-
- # proxy the PHP scripts to Apache listening on 127.0.0.1:80
- #
- #location ~ \.php$ {
- # proxy_pass http://127.0.0.1;
- #}
-
- # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
- #
- #location ~ \.php$ {
- # root html;
- # fastcgi_pass 127.0.0.1:9000;
- # fastcgi_index index.php;
- # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
- # include fastcgi_params;
- #}
-
- # deny access to .htaccess files, if Apache's document root
- # concurs with nginx's one
- #
- #location ~ /\.ht {
- # deny all;
- #}
- }
-
-
- # another virtual host using mix of IP-, name-, and port-based configuration
- #
- #server {
- # listen 8000;
- # listen somename:8080;
- # server_name somename alias another.alias;
-
- # location / {
- # root html;
- # index index.html index.htm;
- # }
- #}
-
-
- # HTTPS server
- #
- #server {
- # listen 443 ssl;
- # server_name localhost;
-
- # ssl_certificate cert.pem;
- # ssl_certificate_key cert.key;
-
- # ssl_session_cache shared:SSL:1m;
- # ssl_session_timeout 5m;
-
- # ssl_ciphers HIGH:!aNULL:!MD5;
- # ssl_prefer_server_ciphers on;
-
- # location / {
- # root html;
- # index index.html index.htm;
- # }
- #}
-
- }
(3)启动Nginx ----运行nginx.exe
3、测试
(1)访问接口包 ---如http://localhost/resource
(2)访问自己的项目 ---如http://localhost/portal
看下代理有没有成功
(3)测试下接口能不能调通,返回数据
(4)看项目中调接口的地方有没有数据显示
参考http://www.imooc.com/article/40074
https://blog.csdn.net/itcats_cn/article/details/82318092
https://blog.csdn.net/weixin_42039396/article/details/80040099
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。