问题引发思考,思考提升学习
1:什么是跨域问题
2:为什么会有跨域问题
3:跨域是为了什么
4:如何跨域
5:不同跨域方式的优缺点,如何选择正确的跨域方式
6:实现跨域应当注意什么
-
跨域问题
由于浏览器同源策略,发送请求url的协议、域名、端口三个方面任意一与当前页面不相同即跨域。
-
为什么会有跨域问题
1:什么是源
源(origin)就是协议、域名和端口号
2:什么是同源策略
因为存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。
如果浏览器没有同源策略,会存在什么样的安全问题呢。下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:
如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:
- 做一个假网站,里面用 iframe 嵌套一个银行网站
http://mybank.com
。 - 把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
- 这时如果用户输入账号密码,我们的主网站可以跨域访问到
http://mybank.com
的 dom 节点,就可以拿到用户的账户密码了。
如果 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:
- 用户登录了自己的银行页面
http://mybank.com
,http://mybank.com
向用户的 cookie 中添加用户标识。 - 用户浏览了恶意页面
http://evil.com
,执行了页面中的恶意 AJAX 请求代码。 http://evil.com
向http://mybank.com
发起 AJAX HTTP 请求,请求会默认把http://mybank.com
对应 cookie 也同时发送过去。- 银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。
- 而且由于 Ajax 在后台执行,用户无法感知这一过程。
因此,有了浏览器同源策略,我们才能更安全的上网。
同源策略主要带来三个方面的限制:
1、cookie,localstorage和IndexDB无法读取
2、DOM无法获取
3、Ajax请求不能发送
由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域
-
跨域是为了什么
为了能够使得使用cookie,让浏览器能够将sessionid放入到request。服务端能够识别是那个用户登录的。从而跨域也可以请求资源,并且不需要登录。
-
如何跨域
跨域的方法:jsonp,jquery,ifram,cors。应用比较广泛的是cors,下面我们主要围绕cors来介绍如何跨域。
cors跨域:跨来源资源共享(Cross-Origin Resource Sharing(CORS)):
1:浏览器端支持情况
- IE8 和 IE9 通过 XDomainRequest 插件支持CORS,IE10 开始则完全正常支持CORS。
- Firefox 3.5 支持跨域 XMLHttpRequests 与 Web Fonts,较旧版本上某些请求会有限制。 Firefox 7 支持 WebGL 纹理的跨域 HTTP 请求,而 Firefox 9 新增支持使用 drawImage 方法,将图形绘制于 canvas 中。
2:服务端CORS支持,两种跨域请求
浏览器将CORS请求分成两类:简单请求(simple request)和预检请求。
同时满足以下条件,那么就是简单请求
(1) 请求方法是以下三种方法之一: HEAD GET POST
(2)HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
(3)没有事件监听器被注册到任何用来发出请求的 XMLHttpRequestUpload 上(经由 XMLHttpRequest.upload 方法取得)上(4)请求中没有 ReadableStream 类型的内容被用于上传。
通常情况下主要涉及条件(1)和条件(2)
如果不满足上述条件任何一个,那么它就是预检请求。
3:简单请求
浏览器发现自己发送的是简单跨域请求,则会只发送一次HTTP请求。相较于同源请求,CORS简单请求会在头信息中额外增加一个Origin字段。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
4:预检请求
不满足简单请求条件之一的即是非简单请求。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)
「预检(preflighted)」请求会先用HTTP 的OPTIONS 方法请求另一个域名资源,确认后续实际(actual)请求能否可安全送出。由于跨域请求可能会携带使用者的信息,所以要先进行预检请求。
先看请求,Access-Control-Request-Method告诉服务器发的请求是POST请求,Access-Control-Request-Headers通知自己带有X-PINGOTHER自定义header
再看响应,Access-Control-Allow-Origin这个与前面类似,Access-Control-Allow-Methods这里说明支持POST/GET/OPTIONS方法,Access-Control-Allow-Headers这里说明允许X-PINGOTHER自定义header,Access-Control-Max-Age用来指定本次预检请求的有效时间,86400是24小时也就是一天。
一旦服务器通过了”预检”请求,在Access-Control-Max-Age指定的时间内,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
5:HTTP跨域请求标识
Origin
Origin 字段表示了跨域请求的来源或者预检请求的来源。在任何跨域请求中,一定要携带Origin字段
Access-Control-Request-Method(仅在预检请求中)
Access-Control-Request-Method 是用在预检请求中,告诉后端server实际请求用的HTTP方法
Access-Control-Request-Headers(仅在预检请求中)
Access-Control-Request-Headers标识用于预检请求中,它会告诉后端server自己所携带的自定义header字段有哪些
6:HTTP跨域响应标识
Access-Control-Allow-Origin
跨域响应会携带该字段,若服务器允许所有uri来访问自己的资源,那么则该字段为*;若要允许http://www.qq.com访问该资源,则为Access-Control-Allow-Origin: http://www.qq.com
Access-Control-Expose-Headers
Access-Control-Expose-Headers表示服务器允许浏览器从响应中解析哪些header字段
Access-Control-Max-Age
Access-Control-Max-Age表示预检请求结果请求成功后,多长时间内非简单请求可以不需要再发预检请求,可以继续直接使用跨域请求请求资源
Access-Control-Allow-Methods(仅在预检请求响应中)
Access-Control-Allow-Methods表示服务器访问操作该资源允许哪些方法
Access-Control-Allow-Headers(仅在预检请求响应)
Access-Control-Allow-Headers表示在访问这个域资源的时候,预检请求响应中哪些header字段可以在跨域请求中使用
后端代码实现
web.xml配置
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.web.filter.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
过滤器filter
package com.web.filter.CorsFilter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
public class CorsFilter implements Filter {
public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
public static final String OPTIONS = "OPTIONS";
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (isCorsRequest(httpRequest)) {
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods",
"POST, GET, PUT, DELETE");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
// response.setIntHeader("Access-Control-Max-Age", 1728000);
httpResponse
.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Authorization");
if (isPreFlightRequest(httpRequest)) {
return;
}
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
public boolean isCorsRequest(HttpServletRequest request) {
return (request.getHeader(HttpHeaders.ORIGIN) != null);
}
/**
* Returns {@code true} if the request is a valid CORS pre-flight one.
*/
public boolean isPreFlightRequest(HttpServletRequest request) {
return (isCorsRequest(request) && OPTIONS.equals(request.getMethod()) && request
.getHeader(ACCESS_CONTROL_REQUEST_METHOD) != null);
}
}
-
不同跨域发送优缺点
CORS与JSONP相比,更为先进、方便和可靠。
1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
对一个简单的请求,没有自定义头部,要么使用GET,要么使用POST,它的主体是text/plain,请求用一个名叫Orgin的额外的头部发送。Origin头部包含请求页面的头部(协议,域名,端口),这样服务器可以很容易的决定它是否应该提供响应。
服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。
Header set Access-Control-Allow-Origin *
为了防止XSS攻击我们的服务器, 我们可以限制域,比如
Access-Control-Allow-Origin: http://beyondLi.com
-
实现跨域应当注意什么
注意nginx时也需要进行跨域设置,前端设置跨域设置,预检测是发送request-method为options。直接返回允许跨域,允许跨域的域名和跨域的方法
nginx配置
nginx添加头
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
nginx对OPTIONS方法,在nginx上将OPTIONS方法返回200,而不是405或403。如果在nginx设置了options的返回,也就不会有filter的什么事情了。
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
return 200;
}
或
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
#在location处添加以下内容
if ($request_method = 'OPTIONS') {
return 200;
}
ajax设置xhrFields和crossDomain
$.ajaxSetup({
global : true,// 默认就是true ,触发全局事件
type : "POST",
contentType : "application/x-www-form-urlencoded;charset=utf-8",
timeout : 10000,
dataType : 'json',
xhrFields:{
withCredentials:true
},
crossDomain:true,
statusCode : {
310 :function(){//未登录
//layer.msg("未登录");
location.href = publicContextPath + "/wechat/oauth2.do?redirectUrl=" + encodeURIComponent(location.href);
},
404 : function() {
layer.msg("请求路径错误");
},
500 : function() {
layer.msg("服务器出了点问题");
}
},
// 同步设置
//async : false
});