使用nginx代理解决跨域问题
先说说跨域这事情吧。早在13年,我刚接触前端开发的时候就遇到了跨域,那时候刚开始流行前后端分离。解决跨域就是直接用get jsonp。还是小白的我,也没有去想跨域的其它解决方式和为什么要采用这种解决方式。
最近,做一个二次开发的项目,也碰到了用网页请求http post,浏览器跨域,不能获取返回数据的问题,所以再次来梳理下这个跨域,为什么最后选择了nginx代理。
首先,什么是跨域呢?首先需要了解的是同源和跨源的概念。对于相同源,其定义为:如果协议、端口(如果指定了一个)和主机对于两个页面是相同的,则两个页面具有相同的源。 只要三者之一任意一点有不同,那么就为不同源。 同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。当一个资源从与该资源本身所在的服务器的域或端口不同的域或不同的端口请求一个资源时,资源会发起一个跨域 HTTP 请求。跨域不一定是浏览器限制了发起跨站请求,而也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。 简单的来说,出于安全方面的考虑,页面中的JavaScript无法访问其他服务器上的数据,即“同源策略”。而跨域就是通过某些手段来绕过同源策略限制,实现不同服务器之间通信的效果。
跨域的解决方案也有很多种。
类型一:有些浏览器可以设置 ,降低它的安全性。但是对于一个网站,要求设置浏览器是不切合实际的。
类型二:直接用form方式 ,这种情况下不是ajax请求,而是直接访问目标地址了,不存在跨域问题,但是这个页面已经跳转了。而我们想实现的只是取另外一个地址的数据到本地显示而已。
类型三:服务端语言是能够处理的情况下。
1、CORS 是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。其需要服务端和客户端同时支持。
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。 Access-Control-Allow-Origin 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。 Access-Control-Allow-Credentials 该字段可选。 Access-Control-Expose-Headers 该字段可选。
可以说这种办法主要在header上下功夫,设置Access-Control-Allow-Origin为所有*允许访问。虽然说它支持所有的请求方式,post,delete,put等等,但是它不能兼容ie6,7等等。
例如下图的nodejs express 例子:
2、服务端的http ajax请求全部改为 get jsonp方式 。该方式能够兼容老式浏览器。
3、iframe window.name 这种传值得方式很巧妙,兼容性也很好。但是在要访问数据的地址那个服务器要有一个空的中间页面拿来用。
4、postMessage , html5 window.postMessage。 同iframe window.name有点像,也是需要服务端有个空的html拿来接收数据。而且现在的postMessage兼容性也不好。
5、document.domain 修改为顶级域名。
6、 WebSocket ,协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
类型四:不是简单的前后端。假如有个第三方的api,自己有一个网站前端,一个网站后端。
1、自己的网站端和后端源码放在同一个服务端口和目录下,不存在跨域。当直接用网站前端的http访问第三方api,浏览器跨域。此时,改为由网站后端的服务端语言访问,做个中间人,将访问的数据给网页前端。
2、网站前端和后端不是同源的,采用以上的跨域方案,譬如CORS。同样的网站后端做中间人,访问第三方api,再转给网页前端。
3、使用nginx 反向代理解决跨域问题。 网站前端访问nginx服务的地址,nginx设置代理地址为访问第三方api地址,当访问代理地址的时候,浏览器访问的是nginx服务的地址,实际是访问第三方api地址。
注意:此时,如果目录下有个proxy.html,因为设置代理地址是/proxy,碰到这个地址就被转到”“,所以要访问proxy.html是访问不到的。
4、使用nginx代理地址是解决生产环境发布的问题了,那么我在开发的时候使用angular这样需要打包的框架怎么办呢。当然在开发环境下,angular也是由类似代理地址的解决方案的。
(1)创建配置代理文件:假设后端服务的访问地址为,我们可以创建一个proxy.conf.json文件,放在package.json同目录下。
(2)改写package.json文件 ,采用--proxy-config命令(angular自带的命令)。
(3)ajax访问代理地址
此时, 执行 npm start ,即可发现,浏览器访问 的同源地址,实际上是访问.
angular在开发环境下代理地址的方法类似在生产环境下使用的nginx代理。但是测试angular是有一个/api代理地址的巧合。刚好第三方api上面的地址有个api,才能使用这个地址,并且能够简写一个api,才能成功访问,如果更改为其它的,譬如proxy,就测试失败。而且proxy.conf.json文件下的设置也只能是域名和端口。所以,本人测试,这或许是个巧合或者是缺陷。
五、其它
当然,跨域这个算是历史性的问题,以后也会存在这个问题。除了上面各种方法,以及根据各种方法使用的场合,还有许多其它的方法。例如各大流行框架react,vie应该也有像angular一样,能够处理跨域的开发环境方案,接下来,还是要继续学习和积累。
后端使用springboot+tio-http-server,前端使用element-ui+websocket,如何解决跨域问题?后端已支持跨域
最便捷的还是使用nginx反向代理吧。
例如,假设后端的ip和端口号为:192.168.111.222:8080,前端的ip和端口号为:192.168.111.222:8001,此时前后端端口不一致导致跨域。
部署一个ngnix,修改ngnix安装目录下的/config/nginx.conf文件,刚安装的ngnix的配置文件里默认应该为:
在http的第一个server节点中后面再添加第二个代理项:
直接将第一组server节点复制一遍,然后修改其中的值为:
server {
listen 8001;
server_name 192.168.111.222;
location / {
root html;
index index.html index.htm;
}
location /api/ {
proxy_pass ;
proxy_set_header Host $http_host;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
这样,当前端访问时,nginx会将请求代理到后端地址
这是我某个nginx的配置项,用于获取本机的文件,模拟实现文件服务器:
配置好ngnix后,你前端工程里访问后端接口时,只需要将请求url修改为即可。
例如你原来的登录后端接口可能是访问,你要修改为访问
在vue项目中配置proxy解决跨域问题
1.问题:
后端给的接口是:.前端在本地开发中调用该接口跨域。
2.解决方案:
* 在webpack中配置proxy。如下图所示
如上:
1. target是你要代理的域名,必须要加上http。
2. 这里用'/api'代替target里面的地址,组件中调用接口时直接用'/api'代替。比如我要调用'.直接写'/api/news'即可
3. changeOrigin必须为true
* 在页面中请求写法如下:
开心吧。数据终于拿到啦!!!
我们知道,vue2.0项目中的static文件夹存储的是静态资源,可以直接被访问到的,如果我们将mock的数据放入static文件夹,在浏览器中直接访问发现是可以访问到数据的。于是我们在页面将mock数据的请求写成下面这样:
则需要在webpack中做如下配置:
注意这里的http一定不能省略,否则数据请求不到。这样做的好处是在本地开发时mock的接口和线上的接口一致,避免上线前大量改动接口名称。
关于跨域的问题
一、在前端开发过程中,如果准备开发富应用,跨域的问题将会随之而来。
我们先看看什么是跨域呢:
所谓跨域,或者异源,是指主机名(域名)、协议、端口号只要有其一不同,就为不同的域(或源) 。出于保护用户数据的目的,浏览器有一个最基本的策略就是同源策略,只允许页面内的脚本访问当前域的资源(加载脚本、资源等不受此限制)。
二、如果浏览器厂商不对跨域请求进行处理,会给我们带来什么危害呢?
有心人士(病毒制造者)会利用这个漏洞进行如下攻击:
1. CSRF/XSRF 攻击 ,简单的来讲就是在 b.com 页面中请求 a.com 的接口(浏览器会自动带上 用户在 a.com 的 cookie),从而获取用户的在 a.com 的相关信息。
2. XSS 注入攻击 ,类似于 SQL 攻击,提交含有恶意脚本的数据到服务器,从而达到破坏页面或者获取用户的 cookie。
三、我们了解到了什么是跨域,那我们又应该如何解决呢,现在找到了这些比较权威的文章,大家先品读一下:
1. mozilla 官方网站关于跨域的文章(Cross Origin), HTTP访问控制(CORS)
2. mozilla 官方网站关于浏览器同源策略的简要介绍(Same Origin), 浏览器的同源策略
四、读完这些文章,你打算怎么处理跨域问题呢,我先谈谈自己关于跨域的解决方案:
1. 采用 CORS 协议,直接在 Nginx 中设置允许跨域的 header(也可以在后端的应用程序内设置,不过在 Nginx 入口配置的话更加统一),在 location 配置中直接使用指令 add_header( 官方文档链接 ),示例配置如下:
2. 使用 JSONP,也是需要后端配合,利用“浏览器加载脚本、资源时不受同源策略的约束”这个特性,但是这种方式非常受限制,例如只能使用 GET 请求,不能携带自定义 header 等。
3. 其他的一些方法,例如 window.name, document.domain 以及 HTML5 中的特性 window.postMessage 等
五、其他参考链接
1. 浅谈JS跨域问题
2. 跨域资源共享 CORS 详解----阮一峰
六、声明
现在网络上的知识非常复杂,有些是摘自权威书籍的,有些是作者自己理解然后记录下来的,有些是瞎掰的,所以一定要结合情况多多甄别,对于有权威文档的知识点,建议先参考文档。
后端解决前端跨域请求问题
场景:前后端分离,页面和后端项目部署在不同服务器,出现请求跨域问题。
原因:CORS:跨来源资源共享(CORS)是一份浏览器技术的规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以让网页设计师用一般的 XMLHttpRequest,这种方式的错误处理比JSONP要来的好,JSONP对于 RESTful 的 API 来说,发送 POST/PUT/DELET 请求将成为问题,不利于接口的统一。但另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作。不过现代的浏览器(IE10以上)基本都支持 CORS。
预检请求(option):在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求(一般都是浏览检测到请求跨域时,会自动发起),以检测实际请求是否可以被服务器所接受。预检请求报文中的 Access-Control-Request-Method 首部字段告知服务器实际请求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。
解决方案:
1、创建一个过滤器,过滤options请求。
package com.biz.eisp.sci.util;
import org.apache.commons.httpclient.HttpStatus;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 解决跨域问题
*
*/
public class CorsFilterimplements Filter {//filter 接口的自定义实现
public void init(FilterConfig filterConfig)throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*");
if ("OPTIONS".equals(request.getMethod())){//这里通过判断请求的方法,判断此次是否是预检请求,如果是,立即返回一个204状态吗,标示,允许跨域;预检后,正式请求,这个方法参数就是我们设置的post了
response.setStatus(HttpStatus.SC_NO_CONTENT); //HttpStatus.SC_NO_CONTENT = 204
response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS, DELETE");//当判定为预检请求后,设定允许请求的方法
response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with"); //当判定为预检请求后,设定允许请求的头部类型
response.addHeader("Access-Control-Max-Age", "1"); // 预检有效保持时间
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
2、修改web.xml文件
filter
filter-namecors/filter-name
filter-classcom.biz.eisp.sci.util.CorsFilter/filter-class
/filter
filter-mapping
filter-namecors/filter-name
url-pattern/* /url-pattern
/filter-mapping
3、spring-mvc.xml添加HttpRequestHandlerAdapter http请求处理器适配器。
HttpRequestHandlerAdapter作为HTTP请求处理器适配器仅仅支持对HTTP请求处理器的适配。它简单的将HTTP请求对象和响应对象传递给HTTP请求处理器的实现,它并不需要返回值。它主要应用在基于HTTP的远程调用的实现上。
bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/
Ajax 跨域问题及其解决方案
主流的 前后端分离模式 下,当前端调用后台接口时,由于是在非同一个域下的请求,从而会引发 浏览器 的自我安全保护机制,最终结果是 接口成功请求并响应 ,但 前端不能正常处理该返回数据 。
因此,当 同时满足 以下三个条件的情况下,就会出现跨域问题:
想要彻底解决跨域问题,只需要破坏以上三个条件的任一即可:
添加浏览器启动参数: chrome --disable-web-security ,但是极不推荐这种解决方式。
Jsonp,全称 JSON with Padding ,一种非官方的协议,而是一种约定;前端通过向后台发送 script 类型请求解决跨域,此时接口响应的 application/javascript 类型的数据会作为 callback 函数的参数进行处理。
所以,后台也需要做相应的处理。以 Java 为例,添加如下配置即可:
综上, jsonp 请求存在以下几个弊端:
用 Nginx 或 Apache 来代理调用方的请求( 客户端变更为相对路径请求,而非绝对路径 ),此时对于浏览器来说,由于请求是同源的,因此就不存在跨域问题。
以 Java 应用为例,添加如下全局配置:
如果只想针对某个类下的接口,或者是某个具体的接口配置允许跨域,只需要在相应的地方添加注解 @CrossOrigin 即可。
如果配置了 nginx 作为代理服务器,那么只需要为 nginx 添加支持跨域请求即可:
Q1:浏览器在执行跨域请求时,是先执行后判断,还是先判断后执行?
A1:都有可能,这需要根据所发送的请求是 简单请求 还是 非简单请求 来判断;如果是非简单请求,浏览器每次在执行真正的请求之前,还会先发送一个 options 请求方式的预检命令【 可设定缓存时长,取消每次请求都要预检,提高效率,参考上面的服务端配置 】。关于两种请求的区分及定义,参考下图说明:
Q2:如果是允许带( 被调用方 ) cookie 的跨域请求,此时服务端同样配置为 Access-Control-Allow-Origin 等于 * ,前端是否还可以请求成功?
A2:不可以,此时要将 Access-Control-Allow-Origin 指定为 调用方 具体的域【 可以先取得调用方的域再动态配置,这样就不存在多个域请求的限制问题 】,并且添加配置 Access-Control-Allow-Credentials 为 true 。