CORS ———— 跨域解决方案之二
其他跨域方案请看:
以下介绍CORS跨域解决方案
一、什么是CORS?
CORS (Corss-Orign Resource Sharing) 是W3C工作草案,是一份浏览器技术的规范。定义了跨域资源访问时,浏览器和服务器之间如何通信,使用自定义的http头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否。CORS在现代浏览器都支持,使用和普通的ajax没有任何区别,关键是只要服务器实现CORS接口。
二、如何使用
现代浏览器都支持CORS,所以前端不需要做任何改变,即使普通的ajax,只需要服务器实现CORS接口;http如何请求服务器,我们不会有如何感觉。
三、服务端处理流程
HTTP请求
1、查看http头部是否有Origin,没有直接结束,有到第2步。
2、查看Origin是否有效,无效则返回403,有效则第3步。
3、查看method是否有效,无效则返回403,是有效则第4步。
4、查看是否是OPTION请求,不是则简单请求处理,是则预检处理。
简单请求处理:
返回Allow-Origin, Allow-Credentials等,并返回正常内容
预检处理:
返回Allow-Origin, Allow-Credentials等,内容为空,只有当浏览器下一次发出实际http请求才返回内容
四、客户端处理机制
浏览器将CORS请求分成两类:简单请求和预检请求(非简单请求),不需要做其他设置
划分的依据为:
(1) 简单请求(需要同时满足下面两个条件)
请求方法是:HEAD ,GET,POST 三者之一
HTTP头信息不超过以下字段:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type(只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain)
简单请求时,浏览器会直接发起一个跨域请求,并在请求头中携带上Origin,用来说明本次请求来自哪个源。服务器端接受到请求后,根据头信息来判断是否允许跨域,返回一个正常的HTTP响应(如果允许,则响应头携带Access-Control-Allow-Origin等字段,并返回正常内容)。浏览器根据返回的响应头是否携带Access-Control-Allow-Origin等字段判断请求是否请求成功,没有则抛出异常。注意:请求是否成功不能通过状态码来确定,因为有可能也是200,但是请求拒绝了。
异常:
正确:
(2) 非简单请求
只要不能同时满足简单请求中任意一个条件的就是非简单请求,如果预检成功,浏览器实际一共会发送两次请求。
分别:
option请求
预检成功则发送正常的post请求
五、服务器端配置
服务器端可以在多个地方设置,比如ngnix、oss、cdn、web服务器等
主要涉及到的是:
Name | Required | Comments |
---|---|---|
Access-Control-Allow-Origin | 必填 | 允许请求的域,比如:http://www.baidu.com或者所有都允许* |
Access-Control-Allow-Methods | 必填 | 允许请求的方法,比如:get、post、put、delete,多个用逗号分割,或者允许所有* |
Access-Control-Allow-Headers | 可选 | 预检请求后,告知发送请求需要有的头部 |
Access-Control-Expose-Headers | 可选 |
CORS请求时,xmlhttprequest默认只能拿到6个基本字段:Cache-Control、 Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿 到其他字段,就必须在Access-Control-Expose-Headers里面指定。 |
Access-Control-Max-Age | 可选 | 本次预检的有效期,单位:秒;在有效期内不需要发出另一条预检 |
Access-Control-Allow-Credentials | 可选 |
表示是否允许发送cookie,默认false;比如put或delete,浊者content-type为 application/json等的有特殊要求,需要设置为true |
1、web服务器
可以通过添加过滤器,或拦截器等处理
<filter>
<display-name>CORSFilter</display-name>
<filter-name>CORSFilter</filter-name>
<filter-class>com.learn.mybatis.controller.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/home</url-pattern>
</filter-mapping>
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response2 = (HttpServletResponse) response;
response2.setHeader("Access-Control-Allow-Origin", "*");
response2.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
response2.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
response2.setHeader("Access-Control-Max-Age", "10");
chain.doFilter(request, response2);
}
2、ngnix
来源:https://michielkalkman.com/snippets/nginx-cors-open-configuration/
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers **should** be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
3、oss
比如阿里云中,
- 进入OSS管理控制台界面。
- 在左侧存储空间列表中,单击目标存储空间名称,打开该存储空间概览页面。
- 单击基础设置页签,找到跨域设置区域,然后单击设置。
4、cdn
六、带认证的请求
CORS默认情况下是不需要提供cookie,http认证,ssl等的,如果需要携带认证,则需要设置
xmlhttprequest.withCredentials = true;
服务器端需要设置:
Access-Control-Allow-Credentials为true。
不然会报错:
当服务器设置带认证,则Access-Control-Allow-Origin不能设置为*,必须为一个具体的域名。
不然也会报错:
设置正确后,将携带上cookie:
七、错误处理
因为CORS错误,浏览器有可能返回的也是200,所以通过状态码无法判定是否出错。
可以使用onerror监听错误,使用onload检测成功
xhr.onerror = function(){
conlose.log("cors error");
}
八、弊端、缺陷
1、只要服务器Access-Control-Allow-Origin不要设置成*,同时做好认证,在安全性上是比较可靠的,但是在低版本浏览器上并不支持,只能使用jsonp。
2、这点比较重要,CORS的两种请求方式:简单请求和非简单请求;即第一种是先发起请求,然后拦截返回数据;第二种是先请求验证进行拦截,再发起请求。
所以对于简单请求来讲,即使返回数据被拦截,但是实际走过了后台的整个逻辑,即更新数据库的更新数据库等,但是到浏览器端却通过onerror报请求失败。
九、JSONP和CORS比较
Name | Compare | Usage |
---|---|---|
安全性 |
因为JSONP不是规范,所以存在很明显的安全漏洞,比如callback注入等。 但是安全没有绝对性,CORS也同样存在漏洞。CORS简单请求失败也会更新数据,这个漏洞在简单请求中存在。 |
|
兼容性 | JSONP兼容型很强,CORS只支持现在浏览器,低版本ie等不支持 | 低版本使用JSONP,现在浏览器推荐CORS |
请求方式 | JSONP只能使用GET,CORS支持所有的http请求 | 有增删改查,推荐使用CORS |
错误处理 | JSONP错误处理不完善,CORS可以监听onerror,并通过浏览器控制台查看 | |
复杂度 | JSONP发送一次get请求,CORS非简单请求会发送两次,第一次预检 |
参考:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS