浏览器限制了什么?

浏览器对js中非同源的请求做了限制,所以不包含 文件加载中src的请求,以及form表单的提交
提交form表单到另外一个域名,原来页面是无法获取新页面的内容,或者说form提交后不需要返回,但是ajax是需要返回的。

源的更改

大概就是,通过document.domain 将当前域名修改为父级域名(只能修改为父级域名)去获取父级域名下的信息

跨域请求解决方案

JSONP

通常一些静态文件不受同源策略的影响:script/css/img

  1. <script src="..."></script>
  2. <img src="...">
  3. <video src="..."></video>
  4. <audio src="..."></audio>
  5. <embed src="...">
  6. <frame src="...">
  7. <iframe src="..."></iframe>
  8. <link rel="stylesheet" href="...">
  9. <applet code="..."></applet>
  10. <object data="..." ></object>

利用标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。

过程:

  1. 声明全局的回调函数 window.callbackFunction
  2. 创建标签,标签的src 指向跨域的资源地址,url?params+callback = callbackFunction
  3. 向dom中添加该标签,dom.appendChild 添加即调用
  4. 服务器接收到请求,将需要返回的数据放在callback中 callbackFunction(data)

java 中 使用 response.getWriter().print(callbackFunction+”(“+resultJSON.toString(1,1)+”)”)

  1. 前端接收到返回,执行回调,removeChild
  1. function jsonp({ url, params, callback }) {
  2. return new Promise((resolve, reject) => {
  3. let script = document.createElement('script')
  4. window[callback] = function(data) {
  5. resolve(data)
  6. document.body.removeChild(script)
  7. }
  8. params = { ...params, callback } // wd=b&callback=show
  9. let arrs = []
  10. for (let key in params) {
  11. arrs.push(`${key}=${params[key]}`)
  12. }
  13. script.src = `${url}?${arrs.join('&')}`
  14. document.body.appendChild(script)
  15. })
  16. }
  17. jsonp({
  18. url: 'http://localhost:3000/say',
  19. params: { wd: 'Iloveyou' },
  20. callback: 'show'
  21. }).then(data => {
  22. console.log(data)
  23. })

安全以及防范

1、有类似于callback的参数传递函数名
2、没有类似于callback这类参数,但是可以通过fuzz找到可能隐藏的这类参数,从而利用
防范
问题:后端getWriter 在response 添加字节流,(getWriter 也可以直接返回html 或者文案)在web容器中组合为http response 返回给浏览器,浏览器直接做了解析? 怎么做的?
设置了content-type=text/html,浏览器遇到此种类型的文件时会自动调用html解析器对文件进行处理

弊端

jsonp只支持get请求
在调用失败的时候不会返回http的状态码

CORS

Cross-Origin Resource Sharing 跨域资源共享

原理

CORS 是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。
CORS 是 w3c的标准,浏览器与服务端开发的一些交互行为
image.png
比如,服务端约定,给A同学开了白名单,A同学拿着自己的身份证(请求地址)来请求,就不会受到同源的限制
通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求

简单请求

(1) 请求方法是以下三种方法之一:

HEAD
GET
POST

(2)HTTP的头信息不超出以下几种字段:

Accept

Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain**
application/x-www-form-urlencoded、multipart/form-data 为表单格式的数据

兼容表单

这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。
form请求会刷新浏览器,不会把数据返回给js,历史原因认为它是安全的,但实际上它是发送了数据的
浏览器对这两种请求的处理,是不一样的。

浏览器处理:

对于简单请求,浏览器直接发出CORS请求。在头信息之中,只增加一个Origin字段。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
另一方面如果服务端同意客户端发送cookie:Access-Control-Allow-Credentials: true,需要设置,客户端才会发送cookie

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

复杂请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

Ngnix代理

https://www.cnblogs.com/PengfeiSong/p/12993446.html
以ngnix为例看跨域的解决配置

ngnix配置地址代理

 server{
        listen 5000;
        server_name serverHostName;


        location = / {
            proxy_pass http://serverHostName:5501/xx.html;
        }

        location /test {
            proxy_pass http://serverHostName:3000/test;
        }
    }

客户端js

http.get('http://serverHostName:5000/test').then(response=>{})

客户端发送请求http://serverHostName:5000/test 到代理服务器,代理服务器开了个 5000端口监听请求,代理配置了 / 和 /test 两种路径,最终发送请求到 http://serverHostName:3000/test 拿到结果 返回给 浏览器

ngnix配置响应头response-hesder

 server{
        listen 5000;
        server_name localhost;
        location = / {
            proxy_pass http://localhost:3000/;
        }
        location /test {

            #   指定允许跨域的方法,*代表所有
            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 204;
            }
        }
    }

客户端js

http.get('http://serverHostName:5000/test').then(response=>{})

客户端发送请求http://serverHostName:5000/test 到代理服务器,代理服务器开了个 5000端口监听请求,拦截/test 的请求,对浏览器的预检 option请求做校验,校验通过给浏览器返回 204,在response header中设置跨域参数,告诉浏览器通过了跨域的校验。
跟在服务端用java,node…加的response-header一样,好处就是对于一类请求可能不在一个服务里,在网关统一配置,减少服务端的重复工作。

nodejs代理

原理跟ngnix相同,只不过ngnix是天然的入口,代理这个事情比较专业,nodejs也可做这件事而已

postMessage

实现页面和页面之间的通信,用于页面和iframe之间,他是window的api

WebSocket

原理是利用http头的方式,请求时在头部添加origin,只要服务端支持,浏览器支持,就可以进行跨域通讯,但它的连接是长连接的,在需要服务端推送时可使用此方案。

dev-server

在开发环境(我们为了模拟浏览器从服务器获取资源进行渲染,但开发阶段服务器并未部署前端代码,我们在本地开了个服务模拟服务端,浏览器访问这个模拟的本地服务端,进行开发,前端通常叫dev-server)
webpack 中的webpack-dev-server,vue-cli 以及相关打包工具 和 脚手架等,基本都能起个dev-server,webpack和vue-cli 中提供了 关于 proxy 的配置
webpack:

devServer: {
    contentBase: path.resolve(__dirname, '..'),
    publicPath: path.resolve(__dirname, '..', '/', router.path),
    compress: true,
    port: 9001
    proxy: {
      '/fuwu1': {
        target: `http://${env}.fuwu1.com/`,
        pathRewrite: { '^/fuwu1': '' },
        changeOrigin: true,
        onProxyReq: req => {
          req.setHeader('x-weimai-token', token);
        },
        onProxyRes: res => {
                }
      },
      '/fuwu2': {
        target: `http://${env}.fuwu2.com/`,
        pathRewrite: { '^/fuwu2': '' },
        changeOrigin: true
      }
    },
  },

在浏览器 请求 /fuwu1/接口1 会被 本地服务转发 到 http://${env}.fuwu1.com/接口1
如果不设置pathRewrite 就是 http://${env}.fuwu1.com/fuwu1/接口1
在这里也可以对请求响应的上下文进行更改 和 处理
浏览器的 请求永远都是到本地服务器 http://localhost:9001/fuwu1/接口1
vue-cli的配置同理
他们都是集成了 http-proxy-middleware ,他封装在 http-proxy
源码 https://github.com/http-party/node-http-proxy