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

栏目: 服务器 · Nginx · 发布时间: 5年前

内容简介:跨域是指去向一个为非本origin(协议、域名、端口任意一个不同)的目标地址发送请求的过程,这样之所以会产生问题是因为浏览器的同源策略限制。 那什么是同源策略呢?同源 —— 如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(origin)。

跨域是指去向一个为非本origin(协议、域名、端口任意一个不同)的目标地址发送请求的过程,这样之所以会产生问题是因为浏览器的同源策略限制。 那什么是同源策略呢?

同源策略

同源策略

同源 —— 如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(origin)。

同源策略实际上又做了什么呢

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

而mdn官方解释中的恶意交互其中一种形式就是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。

在一些安全性要求高的网站,同源策略还是有存在的必要的,大部分需要跨域实现的请求也最好设置一定的限制。

2.跨域问题的解决方案

1.jsonp

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

String jsonp = request.getParameter("jsonpcallback");
     return jsonp+"("+json数据格式+")"
复制代码

客户端用法如下:

<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的修改是否在后端允许范围内。 跨域行为上,不遵守上述简单请求的修改都会被判断为预检请求 首先methods设置 PUTDELETECONNECTOPTIONSTRACE 会导致预检请求 除了上述以外,常用的在headers中设置token也很容易导致预检 预检的请求就需要后端设置更多的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兼容性如下:

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

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反代也是一种思路


以上所述就是小编给大家介绍的《前端跨域问题解决方案(基于node与nginx)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java 8实战

Java 8实战

厄马(Raoul-Gabriel Urma)、弗斯科(Mario Fusco)、米克罗夫特(Alan Mycroft) / 陆明刚、劳佳 / 人民邮电出版社 / 2016-4-1 / CNY 79.00

本书全面介绍了Java 8 这个里程碑版本的新特性,包括Lambdas、流和函数式编程。有了函数式的编程特性,可以让代码更简洁,同时也能自动化地利用多核硬件。全书分四个部分:基础知识、函数式数据处理、高效Java 8 编程和超越Java 8,清晰明了地向读者展现了一幅Java 与时俱进的现代化画卷。一起来看看 《Java 8实战》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具