前言
在前后端分离的项目中,前端调用后端接口时可能会遇到跨域问题,此时,我们脑袋中会冒出很多问号:
- 什么是跨域?
- 为什么会有跨域?
- 如何解决跨域?
同源策略
在聊跨域之前,我们不得不先了解一下同源策略。
什么是同源策略?
同源策略是指在 Web 浏览器中,两个网页必须同源(有相同的协议、主机名和端口号),才允许其中一个网页脚本访问另一个网页的数据。
以http://lovike.cn/page/1此 URL 为例,协议使用http,域名为lovike.cn,端口是80(默认省略),下表将其与其他 URL 相比判断它们两者是否同源:
| URL | 是否同源 | 原因 |
|---|---|---|
http://lovike.cn/page/2 |
是 | |
https://lovike.cn/page/1 |
否 | 协议不同 |
http://lovike.com/page/1 |
否 | 域名不同 |
http://lovike.cn:8080/page/1 |
否 | 端口不同 |
为什么需要同源策略?
同源策略使用目的是为了保证用户信息的安全,用于防止恶意网站窃取敏感数据。
设想这样一种情况:A 网站是一家银行,用户登录以后,又去浏览 P 网站,若 P 网站可以读取 A 网站的 Cookie,可能会发生用户不希望出现的事情。
比如,若 Cookie 包含隐私信息,这些隐私就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,若用户没有退出登录,其他网站就可以冒充用户,偷取你的财产。
由此可见,”同源策略”是有意义的,否则 Cookie 可以共享,互联网就毫无安全可言了。
那么,为了安全,同源策略又做了什么事情呢?
同源策略的想法
同源策略为了保证信息安全,当发现两非同源网站需要进行相互访问,将限制浏览器的以下三种行为:
- ① Ajax 请求发送后不允许读取响应结果
- ② 无法读取对方的 Cookie、LocalStorage、IndexDB
- ③ 无法获取对方的 DOM 进行操作(主要包括 iframe、canvas)
跨域
既然知道了什么是同源策略,那么接下来就开始研究正主——跨域。
什么是跨域?
假设现在有 A 网站和 B 网站,当 A 网站向 B 网站发起一个 Ajax 请求,由于 A 、B 网站非同源(协议、域名、端口三者之间任意一个不同),虽然 B 网站后台会执行请求,但响应返回时会被浏览器拦截,不允许响应结果被 A 网站读取,这就是跨域问题。
因此,跨域是一种浏览器的同源安全策略,即浏览器单方面限制脚本的跨域访问。
很多人误认为资源跨域时无法请求,实际上,通常情况下请求是可以正常发起的(部分浏览器存在特例),后端也将正常进行处理,只是在返回时被浏览器拦截,导致响应内容不可使用。可以论证这一点的著名案例就是 CSRF 跨站攻击。
业务场景
我们经常需要在一个域名下的网页中去调用另一个域名中的资源,举个最常见的例子:前后端分离项目中,前端页面需要去调用后端接口获取数据,因为前后端项目端口不同,所以前后端非同源,那么由于同源策略的限制,虽然 Ajax 请求可以发送,但是响应结果被浏览器拦截限制无法读取,此时就会报跨域错误。
此时,浏览器会报如下错误:
1 | login:1 Access to XMLHttpRequest at 'http://localhost:8080/api/profile' from origin 'http://localhost:7080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. |
虽说同源策略进行了该限制,但我前端页面就是需要调用后端接口拿到数据并渲染给用户啊!这我拿不到数据还给用户看个毛啊!
这样做的产品自然无法达到预期效果,那么我们就需要解决跨域问题啦!
解决方案
跨域问题的解决方案很多,可以考虑从以下 3 个角度解决:
- 前端—— JSONP
- 后端—— CORS
- Nginx 服务器代理
JSONP(了解)
JSONP(JSON With Padding)是一种非官方的解决方案。
由于浏览器允许一些带src属性的标签跨域,例如,iframe、script、img 等,所以 JSONP 利用 script 标签可以实现跨域。
JSONP 具体如何实现由于笔者为后端开发,并不是专业前端,就不去详细综述了。
缺点
JSONP 的原理很简单,几乎兼容所有浏览器,实现起来也并不困难,但只支持 GET 请求跨域, 局限性较大。对于部分不需要考虑兼容老旧浏览器的系统来说,CORS 的方案显得更为优雅、灵活。
CORS(了解)
跨域资源共享(英语:Cross-origin resource sharing,缩写:CORS)是用于让网页的受限资源能够被其他域名的页面访问的一种机制。[1]
由于一些跨域的请求(特别是 Ajax)常常会被同源策略(Same-origin policy)所禁止,因此 CORS 机制定义了一种方式,为的是浏览器和服务器之间能互相确认是否足够安全以至于能使用跨源请求。比起纯粹的同源请求,这将更为自由和功能性的,且比纯粹的跨源请求更为安全。
CORS 规范中有一组新增的 HTTP 首部字段,允许服务器声明其提供的资源允许哪些站点跨域使用。
通常情况下,跨域请求即便在不被支持的情况下,服务器也会接收并进行处理,在CORS 的规范中则避免了这个问题。浏览器首先会发起一个请求方法为 OPTIONS 的 预检请求,用于确认服务器是否允许跨域,只有在得到许可后才会发出实际请求。
此外,预检请求还允许服务器通知浏览器跨域携带身份凭证。
CORS 新增的 HTTP 首部字段交由服务器控制,比如下面这个字段:1
Access-Control-Allow-Origin
该字段允许取值为:
<origin>:指被允许的站点,使用 URL 首部匹配原则*:匹配所有站点,表示允许来自所有域的请求。
但并非所有情况都简单设置即可,若需要浏览器在发起请求时携带凭证信息,则不允许设置为*。若设置了具体的站点信息,则响应头中的 Vary 字段还需要携带 Origin 属 性,因为服务器对不同的域会返回不同的内容:
在 Spring 项目中,可以通过新建配置类解决该问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* @author Chris Wong
* @since 2020-02-09
*/
public class CorsConfig {
public CorsFilter corsFilter() {
// 1.添加 CORS 配置信息
CorsConfiguration config = new CorsConfiguration();
// 1.1 注意允许的域不要写 * ,否则 cookie 将无法使用,可添加多个域
config.addAllowedOrigin("http://localhost:7080");
config.addAllowedOrigin("http://192.168.1.100:7080");
// 1.2 是否发送 cookie 信息
config.setAllowCredentials(true);
// 1.3 允许的请求方式
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
// 1.4 允许的头信息
config.addAllowedHeader("*");
// 1.5 有效时长
config.setMaxAge(3600L);
// 2.添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
// 3 返回新的 CorsFilter
return new CorsFilter(configSource);
}
}
Nginx 服务器代理(常用)
1 | server { |
上面通过 Nginx 进行以上配置:
- Nginx 监听
app.leeqingshui.com的 80 端口,此端口会反向代理到前端路径下 - Nginx 特殊配置了一个
^~ /api/的访问,代表api开头的地址(访问app.leeqingshui.com/api/**)都转到10.211.55.10:8070,这是我们的后端地址
现在,所有的访问都要走 Nginx:
- 访问
app.leeqingshui.com地址即访问前端 - 前端访问后端只需要访问
app.leeqingshui.com/api下的对应接口就好
作用原理
由于app.leeqingshui.com和app.leeqingshui.com/api属于同源网站,所以此时不会再发生跨域问题。