跨域问题系列文章
1. 同源策略与CORS(跨域请求的起源)
2. SpringBoot2.x整合CORS解决跨域问题(两种方案)
3. 跨域预检请求进行权限认证(复杂跨域请求的处理)
4. Filter返回失败如何使用CORS配置(SendError和setStatus的区别)
1. 同源策略
浏览器的安全基石是“同源策略”,目前所有的浏览器都实行这个策略。
同源策略限制了:从一个源加载的文档或脚本去另一个源进行资源交互。
同源指的是协议://域名:端口 相同,可以理解为一个服务器
同源策略主要是对js脚本有限制,主要表现为下面三点:
- 无法使用js脚本获取非同源Cookie,LocalStorage和IndexDB数据。
- 无法使用js获取非同源的DOM。
- 无法使用js发送非同源的AJAX请求,更确切的话,js可以向非同源的服务器发送请求,不能携带非同源服务器的Cookie,最后服务器返回的数据会被浏览器拦截。
2. CSRF安全问题
如果没有同源策略,可能会造成Cross SiteRequest Forgery 跨站点请求伪造
[可绕斯][赛特][否则瑞]
,攻击者会伪造客户端的请求向服务器进行攻击。
服务器对跨域访问的检查就可以有效避免或限制这个行为。因为正常访问和伪造请求的区别就在于请求的源页面是否是服务器能够识别的页面。
3. CORS解决跨域问题
既然请求头中会携带Origin(请求的源头)字段,那么服务器只需要验证该字段是否符合预期即可。
要解决跨域问题,首先要知道跨域问题产生的原因:
- 浏览器的限制(服务端收到了请求并正确返回);
- 发送的是XMLHttpRequest请求,也就是
XHR
请求; - 请求了不同域的资源;
CORS是一个W3C标准,全程是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发送XMLHttpRequest请求,从而克服Ajax只能同源的限制。CORS基于http协议关于跨域方面的请求,使用时,客户端浏览器直接异步请求被调用端的服务端,在响应头增加响应字段,告诉浏览器允许跨域。
我们可以看到具体的异常:服务端没有设置Access-Control-Allow-Origin
这个响应头从而导致出错,那么通过设置Access-Control-Allow-Origin:*
这个响应头,我们可以解决问题。但是,这种设置能满足所有情况吗?更进一步来说,使用CORS时浏览器如何检查跨域错误?虽然浏览器异常,但是在之前服务端已经接受了请求,那么浏览器是先发出请求再判断的吗?
3.1 浏览器如何检查跨域错误
当浏览器会检测到ajax请求的域和当前域不一致时,会在请求头增加origin
字段,然后检查服务端响应头Access-Control-Allow-Origin
[额老]
,如果不存在或不匹配,则报跨域错误。
3.2 浏览器总是先发出请求,然后根据响应来判断吗?
对于简单请求,是通过Access-Control-Allow-Origin来判断;但是对于非简单请求,浏览器并不是直接请求所需要资源,而是先发出一个预检请求,预检请求通过后才会对所需资源进行请求。
这里涉及到的简单请求和非简单请求的概念,那么简单请求和非简单请求有什么区别呢?MDN 对非简单请求进行了定义,满足下列条件之一,即为非简单请求:
- 使用了下列 HTTP 方法:PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH;
- 使用了除以下首部之外的其他首部:Accept、Accept-Language、Content-Language、Content-Type;
- Content-Type首部的值不属于下列其中一个: application/x-www-form-urlencoded、 multipart/form-data、 text/plain;
- 请求中的 XMLHttpRequestUpload 对象注册了任意多个事件监听器;
- 请求中使用了ReadableStream对象
简单来说,除了我们平时使用最多的 GET 和 POST 方法,以及最常使用的 Accept、Accept-Language、Content-Language 和 类型为 application/x-www-form-urlencoded、 multipart/form-data、 text/plain 的 Content-Type 请求头,其他基本都是非简单请求。对于这些非简单请求,浏览器会发出两个请求,第一个为 OPTIONS 预检请求,预检请求的响应检查通过后才会发出对资源的请求。
但是在生产环境下,如果发送非简单请求,每次两个请求会增加响应时间,为此,W3C标准增加了另外一个响应头Access-Control-Max-Age[啊可谁斯]
,该参数表明了对于非简单请求的预检请求浏览器的缓存时间,在缓存有效期内,非简单请求可以不发送预检请求。另外,可以在服务端设置接收到的方法是OPTIONS时,直接返回200,这也可以加快响应。
3.3 设置 Access-Control-Allow-Origin: * 就行吗
当我们需要发送cookie的请求时,Access-Control-Allow-Origin直接设置为*是无法通过浏览器的检查的,此时,该响应头的值必须与发请求时的域完全一致才行。另外,还需要设置 Access-Control-Allow-Credentials 响应头为true,表示支持cookie的跨域请求。
3.4 CORS请求头和响应头详解
请求头:
- Origin:浏览器发出 Ajax 跨域请求之前会添加此头部,值为发送请求的域;
- Access-Control-Request-Method:使用了除 GET、POST 请求方法之外的方法,浏览器会添加此头部,值为当前请求方法;
- Access-Control-Request-Headers:使用了自定义头部或除了Accept、Accept-Language、Content-Language、Content-Type 之外的头部,浏览器会添加此头部,值为当前的请求方法;
响应头:
- Access-Control-Allow-Origin: 表示服务端允许哪些域请求资源;
- Access-Control-Allow-Methods: 当客户端包含 Access-Control-Request-Method 请求头时,服务端需要响应该头部,值通常由 Reauest 的 header 中 Access-Control-Request-Method 取得;
- Access-Control-Allow-Headers: 当客户端包含 Access-Control-Request-Headers 请求头时,服务端需要响应该头部,值通常由 Reauest 的 header 中 Access-Control-Request-Headers 取得;
- Access-Control-Expose-Headers: 指出客户端通过 XHR 对象的 getResponseHeaders 方法可以获取的响应头有哪些;
- Access-Control-Allow-Credentials: 允许带 cookie 的跨域请求;
- Access-Control-Max-Age: 预检请求的缓存时间
3.5 java如何实现CORS
主要思想是设置响应头
web.xml配置:
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.springmvc.common.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
拦截器配置:
public class CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Token");
response.setHeader("Access-Control-Allow-Credentials","true");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
}
4. nginx解决跨域问题
- 首先明确一个概念,前端项目,后端项目,nginx这是三个server项目,他们之间相互交换数据。
- 三个项目都有自己的ip:port组合,哪怕是在同一台服务器上启动这三个server,他们的port也是可能不同的。
- 同源策略只存在于浏览器,nginx访问后端项目不存在跨域问题。
- 前端项目,无论访问nginx还是访问后端项目,都存在跨域问题。