阅读 8727

前端跨域问题解决方案(基于node与nginx)

1.跨域是什么

  跨域是指去向一个为非本origin(协议、域名、端口任意一个不同)的目标地址发送请求的过程,这样之所以会产生问题是因为浏览器的同源策略限制。看起来同源策略影响了我们开发的顺畅性.实则不然,同源策略存在的必要性之一是为了隔离攻击。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

  我们就拿同源策略隔离的主要攻击之一CSRF为例讲述下同源策略存在的必要性

CSRF

  CSRF,又称跨站请求伪造,指非法网站挟持用户cookie在已登陆网站上实施非法操作的攻击,这是基于使用cookie在网站免登和用户信息留存的实用性,接下来来讲讲正常网站免登的请求流程。 请求流程如下:

  1. 我们进入一个网站,发送登陆请求给后端
  2. 后端接受登陆请求,判断登陆信息是否准确
  3. 判断信息准确后后端后会发送response给浏览器并在response header中加入set-cookie字段
  4. 浏览器接受response返给用户,并将header中的cookie进行保存
  5. 用户关闭当前网站窗口后再次打开后,浏览器会自动将cookie加入request header实现免登

我们设想这样一个场景

  1. 小a登陆了网银网站,小a所在浏览器记录了网银回馈的cookie
  2. 这时他qq上收到个链接,什么澳门赌场,美女荷官,在线送钱的网站b
  3. 他点开那个链接之后,网站b就可以携带浏览器设置的cookie向网银系统上发送请求

  结果不言而喻,轻则信息泄漏,重则钱财损失,而且cookie正常的存储时间是直到关闭浏览器为止,而不是关闭网站,所以很多用户会以为关闭网站了再去打开澳门的网站就安全了emmmm。

  在一些安全性要求高的网站,同源策略还是有存在的必要的,需要跨域实现的请求也最好设置限制,比如设置指定的白名单origin。

2.跨域问题的解决方案

1.jsonp

  最早的解决方案之一就是jsonp,实现方式是通过script标签传递数据,因为script请求不会被同源策略禁止,所以通过script标签去请求跨域数据,并且在script的cb对应func中实现对数据的获取是可行的,当然这种方式需要后端进行配合,后端在前端进行对应请求的时候返回对应的jsonp格式的数据 php案例如下:

<?php
header('Content-type: application/json');
//获取回调函数名
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']);
//json数据
$json_data = '["customername1","customername2"]';
//输出jsonp格式的数据
echo $jsoncallback . "(" . $json_data . ")";
?>
复制代码

  客户端用法如下:

 <script type="text/javascript">
		function callbackFunction(result, methodName)
        {
            ///result 指向对应数据
        }
</script>
<script type="text/javascript" src="http://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"></script>
复制代码

2.CORS

  接下来讲到的就是我们的主角CORS,那么CORS是什么呢?

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求

  CORS又分为简单请求预检请求

简单请求

mdn定义的简单请求就是某些不会触发cors预检的请求。

这里简单描述下简单请求的重点,也就是在实际开发过程中会碰到的主要情况

  1. 设置不会触发预检的Methods : GETHEADPOST。 GET和POST大家都很熟悉,不再赘述,解释下HEAD请求,HEAD就是只发送请求不会收到响应的一种请求方式,日常用的比较少

  2. 简单请求只可以设置如下header如下AcceptAccept-LanguageContent-LanguageContent-Type

  3. Content-Type标头允许的值只能是: application/x-www-form-urlencoded、 multipart/form-data、 text/plain

后端适配方案: 在respones header中添加Access-Control-Allow-Origin

'Access-Control-Allow-Origin''xxx'
复制代码

  Access-Control-Allow-Origin代表允许发送请求的源,参数可以是固定的白名单ip或者通配符,可以用通配符"*",代表接受所有请求。不过有种特殊情况是不能使用通配符的,就是前端请求header中含有withCredentials,withCredentials:true是跨域请求想要携带cookie必须加入的headers配置

预检请求

  预检请求就是在跨域的时候设置了对应的需要预检的内容,结果上会在普通跨域请求前添加了个options请求,用来检查前端headers的修改是否在后端允许范围内。 触发预检请求在跨域开发中会碰到的主要情况如下

  1. 首先methods设置 PUTDELETECONNECTOPTIONSTRACE会导致预检请求
  2. 设置了AcceptAccept-LanguageContent-LanguageContent-Type 之外的headers中任一的配置,比如常见的token:authorization,缓存机制cache-contorl
  3. Content-Type设置了简单请求不允许的值,如常用的application/json

那么预检请求我们需要如何处理呢?

预检请求就需要后端设置更多的respones headers了,常用如下:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
复制代码
  • Access-Control-Allow-Methods代表可接受methods
  • Access-Control-Allow-Headers代表可接受的headers修改
  • Access-Control-Max-Age代表预检的残留时间,代表预检之后可以免预检的时间

除此之外,后端还需要设置对options请求的判断,我在node中间件中添加的判断如下:

if (req.method == 'OPTIONS') {
    res.send(200);
  } else {
    next();
  }
复制代码

  更多详细cors内容可见:developer.mozilla.org/en-US/docs/…

实现CORS的几种方式

  1. 本地代理
  2. nodejs中间件
  3. nginx代理

本地代理

  在dva中的实现方式是在.webpackrc中添加如下代码

 "proxy": {
    "/api": {
      "target": "http://127.0.0.1:8988/",
      "changeOrigin": true,
      "pathRewrite": { "^/api" : "" }
    }
  }
复制代码

  /api代表代理的路径名,target代表代理的地址,changeOrigin代表更改发出源地址为target,pathRewrite代表路径重写,别的脚手架直接加载webpack配置文件即可

nodejs跨域中间件

  具体实现过程我是使用express+http-proxy-middleware

  1. 用express脚手架生成express模具
npm install express-generator -g
express --view=pug myapp
复制代码
  1. 设置一个全局路由拦截
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
if (req.method == 'OPTIONS') {
  res.send(200);
} else {
  next();
}
})
复制代码
  1. 再设置对应的代理逻辑
var options = {
  target: 'https://xxxx.xxx.xxx/abc/req',
  changeOrigin: true,
  pathRewrite: (path,req)=>{
    return path.replace('/api','/')
  }
}
app.use('/api', proxy(options));
复制代码
  1. 进入bin/www中设置对应的端口,或者在process.env.PORT设置port启动值
var port = normalizePort(process.env.PORT || '7002');
复制代码
  1. 启动脚手架
DEBUG=myapp:* npm start
复制代码

启动代理后就可以直接在对应的项目中请求中间件实现跨域了

Nginx跨域代理

  Nginx是国外大神实现用的用于反向代理的异步web服务器 他除了用于反向代理以外还可以用于负载均衡、HTTP缓存 接下来来介绍安装方式 首先安装Nginx要安装Homebrew, 然而我的mac并没有Homebrew,那么还需要先用Ruby安装一下这个包管理器

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
复制代码

  查看下brew是否安装成功,成功后可以顺利的安装Nginx啦

brew -v
brew install nginx
nginx -v
复制代码

  Nginx的常用命令有这些

#查看版本,以及配置文件地址
nginx -V
#查看版本 
nginx -v
#指定配置文件
nginx -c filename
#帮助
nginx -h
#重新加载配置|重启|停止|退出 nginx
nginx -s reload|reopen|stop|quit
#打开 nginx
sudo nginx
#测试配置是否有语法错误
sudo nginx -t

复制代码

  然后就要进入Nginx配置文件

sudo vim /usr/local/etc/nginx/nginx.conf
复制代码

  ps:nginx-http常见配置项如下

http {
    #导入类型配置文件
    include       mime.types;
    #设定默认类型为二进制流
    default_type  application/octet-stream;
    #启用sendfile()函数
    sendfile        on;
    #客户端与服务器连接的超时时间为65秒,超过65秒,服务器关闭连接
    keepalive_timeout  65;
    #是否开启gzip,默认关闭
    #gzip  on;
    #一个server块
    server {
        #服务器监听的端口为80
        listen       80;
        #服务器名称为localhost,我们可以通过localhost来访问这个server块的服务
        server_name  localhost;
        #location块,它存放在server块当中,location会尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择location {}块中的配置来处理用户请求。
        location / {
            #以root方式设置资源路径,它与alias的不同请见下面的 http模块中文件路径定义
            root   html;
            #默认访问的页面,从左依次找到右,直到找到这个文件,然后返回结束请求
            index  index.html index.htm;
            #设置错误页面,对应的错误码是404,错误页面是/Users/user/Sites/404.html
            error_page 404  /404.html;
        }
    }
    include servers/*;
}
复制代码

  接下来就是重要的实现反向代理的方式了 这里介绍的主要展示方式是如何在线上设置代理,所以代理的入口是静态资源

server {
        listen       80;
	server_name  localhost;
	location / {
            root   /Users/abc/dist/;
            index  index.html index.htm;
        }

        location /api/ {
                proxy_pass  https://xxx.xxx.xxx/req/;
        }
}
复制代码

  location中的后的内容会尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择location {}块中的配置来处理用户请求

  本项目中第一个location用于指向静态资源位置 root:目录,index:入口文件,第二个location用于进行api的跨域指向

  如果你想要对不同的端口实现代理,可以设置多个server listen同一个端口,根据server_name判断请求来源,根据location 设置代理去向

  看到这里,如果你没碰到权限问题,那么恭喜你,如果碰到了,往下看tips3

  nginx踩坑:

  1. nginx在每次修改conf后都需要重启,而且需要sudo
  2. nginx -s reload 有的时候会莫名丢失PID,网上很多的方案推荐 nginx -c来解决,实际上reload的时候通常nginx服务还是在跑着的,此时根本无法nginx -c,就会无限的 "address already in use" 此时需要的是杀掉对应的进程。。。。
ps -ef | grep nginx 查询进程号
sudo kill -QUIT 主进程号 杀掉主进程号
sudo nginx 即可
复制代码
  1. nginx权限问题 权限问题最最最最最坑,但是你又说不出什么,因为合情合理,他最主要的显示方式是你的静态资源网站会一直显示为403,通常出现于dist目录与nginx目录分开的情况下,偶尔会出现于你修改了conf后-t的时候 此时需要设置权限 sudo chown -R 'username' /usr/xxx/xxx/run/nginx.pid 坑的是,这个只能解决你 -t的时候缺乏权限的问题 403还需要在nginx.conf增加配置 user your_username staff; 设置nginx的username才能解决403权限的问题。

拓展话题 nginx还可以用于实现多入口

server {
        listen       80;
	server_name  www.aaa.com;
        location /api/ {
                proxy_pass  http://localhost:7001;
        }
}
server {
        listen       80;
	server_name  www.bbb.com;
        location /api/ {
                proxy_pass  http://localhost:7002;
        }
}
复制代码

  nginx还有实现负载均衡的功能,这里就不详细展开了

3.解决方案的对比

对比jsonp和cors,两者优劣如下

  1. json只支持get请求,无法支持复杂的请求
  2. jsonp出现错误的时候,很难去进行错误识别与处理,cors可以正常错误捕捉
  3. jsonp的兼容性比较高,而cors在旧版ie中需要寻找对应的替代方案

cors兼容性如下:

undefined
pc与移动端主要浏览器完美支持,问题就是在ie低版本中需要找寻降级方案

  至于CORS的三种代理方案优缺点主要如下:

  1. 设置webpack-dev-server的proxy最简单,但是通常只能用于本地环境,线上环境通常无法直接代理
  2. node代理书写比较方便灵活,而且不需要过多的学习成本,前端了解一定的后端知识然后写好异常捕捉就可以上手,还可以进行一些业务方面的处理,比如对接收请求进行拦截
  3. nginx实现代理的方案最敏捷,性能相较于node高出不少,nginx本身就以使用少资源,高并发,高效率的处理静态资源和反向代理而闻名,nginx也更加稳定,在处理请求的同时cpu内存使用率低,异步处理的流程能更好的抗住压力,同一个进程能面对万级的连接,nginx的性能解释可以阅览如下: www.mamicode.com/info-detail…

  总结的来说,其实node和nginx都可以实现代理功能,两者都是异步非阻塞模型都可以很好的支持前端的高频率请求,node可以实现对请求的拦截与二次控制,而nginx在实现静态资源代理、反向代理这几个方面更胜一筹,而且nginx还具有高稳定性,高并发支持而占优,两者可以在一定程度上结合,node拦截+nginx反代也是一种思路

思索

  其实nginx并不算前端的知识内容,node才是一个可以无缝衔接的前端代理中间件,但是nginx他本身的优良与和前端请求的紧密结合其实都可以作为前端的知识体系的拓展。

参考资料:

  1. 同源策略 developer.mozilla.org/zh-CN/docs/…

  2. cors developer.mozilla.org/en-US/docs/…

  3. express中文网 www.expressjs.com.cn/

  4. nginx官网 nginx.org/en/docs/

  5. nginx性能解释 www.mamicode.com/info-detail…

关注下面的标签,发现更多相似文章
评论