跨域
同源策略
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。
同源是指 “协议+域名+端口” 三者相同。
即便两个不同的域名指向同一个ip地址,也非同源
同源策略限制内容有:
- Cookie、LocalStorage等存储性内容
- DOM 节点
- AJAX 请求发送后,响应结果被浏览器拦截了
允许跨域加载资源的标签:
1
2
3<img src=XXX>
<link href=XXX>
<script src=XXX>
跨域
当协议、子域名、主域名、端口号中任意一个不相同时,都算不同域。不同域之间相互请求资源,称为跨域。
比如: 应用访问了该应用域名(或端口)以外的域名(或端口)。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
浏览器如何判断该请求是不是跨域请求?
非跨域请求: 在请求头中,只包含请求的主机名(host)。
跨域请求: 在请求头中,会既包含要请求的主机名(host)还包括当前的源主机名(origin),如果两者不一致,那就是跨域请求。
解决跨域的方案
一、 客户端浏览器解除跨域限制(不现实)
浏览器是默认开启跨域安全检查的,可以使用命令行启动浏览器,加上禁止安全检查的参数。
二、 JSONP
JSONP(JSON with Padding)是JSON的一种补充使用方式,利用 <script> 标签请求资源可以跨域的特点,来解决跨域问题。
JSONP请求的类型是JavaScript脚本,而XHR请求的类型是json类型。
特点:
仅支持get方法具有局限性, 不安全可能会遭受XSS攻击。服务端需要修改代码。
实现原理:
声明一个回调函数,函数名当做参数值,传递给跨域请求的服务器,函数形参为要获取目标数据(服务器返回的data)。
创建一个<script>标签,script的src为API接口地址,在这个地址中,向服务器传递该函数名(可以通过问号传参:?callback=show)
服务器接收到请求后,需要进行特殊的处理,接成一个字符串
最后服务器把数据返回给客户端,客户端再调用执行之前声明的回调函数,对返回的数据进行操作
三、CORS (Cross-origin resource sharing)
CORS 需要浏览器和后端同时支持。
浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。只要后端实现了CORS接口,就可以跨域通信。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
- 简单请求
只要同时满足以下两大条件,就属于简单请求:- method: head post get 之一
- header信息不超过这几个字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type (值有限制) - Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。
浏览器发现,这个response header信息没有包含Access-Control-Allow-Origin字段,就知道出错了,
从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。
tips: 这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段:
Access-Control-Allow-Origin (必须)
要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。Access-Control-Allow-Credentials (可选)
一个Boolean。表示是否允许发送Cookie(默认Cookie不包括在CORS请求之中)
同时,前端开发必须在AJAX请求中打开withCredentials属性1
2var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json预检请求 (OPTIONS)
在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
除了Origin字段,”预检”请求的头信息还包括的特殊字段:
Access-Control-Request-Method 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,eg:PUT。预检请求的回应
- Access-Control-Allow-Origin
关键字段。 若设为*,表示同意任意跨源请求。 - Access-Control-Allow-Methods: GET, POST, PUT
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法 - Access-Control-Allow-Credentials
是否允许发送cookie - Access-Control-Max-Age
可选字段。单位为秒。代表在此期间内,不用发出另一条预检请求
- Access-Control-Allow-Origin
浏览器的正常请求和回应
一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
四、node中间层代理
利用node进行接口转发,使用node + express + http-proxy-middleware搭建一个proxy服务器。
在代理服务器中修改响应头信息,实现跨域并允许带cookie:
1
2
3
4
5
6// 如果要发送Cookie,Access-Control-Allow-Origin就不能设为*,
// 必须指定明确的、与请求网页一致的域名
res.header('Access-Control-Allow-Origin', '*')
res.header("Access-Control-Allow-Credentials", true)
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS')
res.header('Access-Control-Allow-Headers','自定义的请求头')
对于非简单请求的预检命令:OPTIONS类型的请求,直接返回200。
http-proxy-middleware里面配置的参数:
- target 代表远程真实服务器的地址
- changeOrigin设置为true,表示将请求转发到target地址上
- pathRewrite 是对请求路径做处理, 例如可以将/api转换成/server/api.
拦截客户端的请求,转到目标服务器。
目标服务器的响应结果再返回给node服务器,node服务器再返回给浏览器.
node中间层代理的其他应用: 首屏加载
把首屏渲染的任务交给nodejs去做,次屏的渲染依然使用浏览器渲染。
服务端渲染,可直接拼接出html字符串,可以大大提高首屏渲染效率,减少用户等待时间。
五、Nginx 反向代理
打开 Nginx 的安装目录下的nginx.conf文件,只需要配置 http 模块下的 server 即可。
- 修改http模块下的server配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16server {
listen 80; # 端口号
server_name localhost; # server name 默认 localhost
#access_log logs/host.access.log main;
location / { # 访问路径匹配规则
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html; # 错误处理
location = /50x.html {
root html;
}
}
location 配置项定义了访问 Nginx 某路径时的匹配规则,后面紧跟的是匹配的路径。
可以直接写绝对路径,可以写正则匹配:
1 | # 当访问 http://localhost/api1 时命中 |
- proxy_pass
location 里配置项有很多,其中一个是 proxy_pass:
意思是将当前命中的 Nginx 接口代理到其他 server 的接口。
例如:将 http://localhost/api 代理到 https://baidu.com/api1
2
3location /api {
proxy_pass https://baidu.com;
}
需要注意的是,proxy_pass末尾的/:
1 | location /api { |
- add_header
Location 配置中的 add_header选项,表示 Nginx 将在 response 中添加一些额外的响应头信息给客户端1
2
3
4
5
6
7
8
9location /api {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
if ($request_method = 'OPTIONS') {
return 200; # Nginx 拦截 OPTIONS 直接响应 200
}
proxy_pass https://baidu.com;
}
- 本文作者:JSZ
- 本文链接:blog.vampuck.com/2022/03/07/cors/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!