什么是跨域
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
CORS即Cross-Origin Resource Sharing,跨域资源共享
CORS分为两种
简单的跨域请求流程
网页:当HTTP请求同时满足以下两种情况时,浏览器认为是简单跨请求
-
请求的方法是get,head或者post,同时Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一个值,或者不设置也可以,一般默认就是application/x-www-form-urlencoded。
-
请求中没有自定义的HTTP头部,如x-token。(应该是这几种头部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)
浏览器:把客户端脚本所在的域填充到Origin header里,向其他域的服务器请求资源。
服务器:根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header,返回结果
浏览器:比较服务器返回的Access-Control-Allow-Origin Header和请求域的Origin,如果当前域已经得到授权,则将结果返回给页面。否则浏览器忽略此次响应。
网页:收到返回结果或者浏览器的错误提示。
总结:对于简单的跨域请求,只要服务器设置的Access-Control-Allow-Origin Header和请求来源匹配,浏览器就允许跨域。服务器端设置的Access-Control-Allow-Methods和Access-Control-Allow-Headers对简单跨域没有作用。
带预检(Preflighted)的跨域请求流程
网页:当HTTP请求出现以下两种情况时之一,浏览器认为是带预检(Preflighted)的跨域请求:
浏览器:发现请求属于以上两种情况,向服务器发送一个OPTIONS预检请求,检测服务器端是否支持真实请求进行跨域资源访问,浏览器会在发送OPTIONS请求时会自动添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。
服务器:响应OPTIONS请求,会在responseHead里添加Access-Control-Allow-Methods head。这其中的method的值是服务器给的默认值,可能不同的服务器添加的值不一样。服务器还会添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。这些取决于服务器对OPTIONS请求具体如何做出响应。如果服务器对OPTIONS响应不合你的要求,你可以手动在服务器配置OPTIONS响应,以应对带预检的跨域请求。在配置服务器OPTIONS的响应时,可以添加Access-Control-Max-Age head告诉浏览器在一定时间内无需再次发送预检请求,但是如果浏览器禁用缓存则无效。
浏览器:接到OPTIONS的响应,比较真实请求的method是否属于返回的Access-Control-Allow-Methods head的值之一,还有origin, head也会进行比较是否匹配。如果通过,浏览器就继续向服务器发送真实请求。 否则就会报预检错误,如下几种:
请求来源不被options响应允许:Failed to load…Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin http://127.0.0.1:8080 is therefore not allowed access.
请求方法不被options响应允许:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
请求中有自定义header不被options响应允许:Failed to load… Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.
服务器:响应真实请求,在响应头中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头,并返回数据。(如果服务器对真实请求的响应另外设置有Access-Control-Allow-Methods,它的值不会生效,个人理解是因为刚刚在服务器响应OPTIONS响应时,就已经验证过真实请求的method是属于Access-Control-Allow-Methods head的值之一)。也可以在响应真实请求时添加Access-Control-Max-Age head。
浏览器:接受服务器对真实请求的返回结果,返回给网页
网页:收到返回结果或者浏览器的错误提示。
总结:也就是说Access-Control-Allow-Methods和Access-Control-Allow-Headers只在响应options请求时有作用,Access-Control-Allow-Origin在响应options请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。
服务器设置OPTIONS响应一般要同时满足这些条件,一是跨域,二是有带预检的请求,三是服务器对OPTIONS响应默认值不符合要求,如果是不存在跨域情况,就不需要在服务器手动设置OPTIONS响应。
XMLHttpRequest支持通过withCredentials属性跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)。浏览器将携带Cookie Header的请求发送到服务器端后,如果服务器没有响应Access-Control-Allow-Credentials Header,那么浏览器会忽略掉这次响应。如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名。
SpringBoot设置跨域
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
@Component
public class MyInterceptor implements HandlerInterceptor {
private final static Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
httpServletRequest.setAttribute("p_real_ip", realIp);
httpServletRequest.setAttribute("begin_nao_time", begin_nao_time);
httpServletResponse.addHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.addHeader("Access-Control-Allow-Methods", "OPTIONS,HEAD,DELETE,GET,PUT,POST");
httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
httpServletResponse.addHeader("Access-Control-Max-Age", "3600");
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
<h2><a id="_0"></a>什么是跨域</h2>
<p>出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)<br />
<strong>当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域</strong></p>
<blockquote>
<p>CORS即Cross-Origin Resource Sharing,跨域资源共享<br />
CORS分为两种</p>
</blockquote>
<h2><a id="_6"></a>简单的跨域请求流程</h2>
<p>网页:当HTTP请求同时满足以下两种情况时,浏览器认为是简单跨请求</p>
<ul>
<li>
<p>请求的方法是get,head或者post,同时Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一个值,或者不设置也可以,一般默认就是application/x-www-form-urlencoded。</p>
</li>
<li>
<p>请求中没有自定义的HTTP头部,如x-token。(应该是这几种头部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)</p>
</li>
</ul>
<p>浏览器:把客户端脚本所在的域填充到Origin header里,向其他域的服务器请求资源。</p>
<p>服务器:根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header,返回结果</p>
<p>浏览器:比较服务器返回的Access-Control-Allow-Origin Header和请求域的Origin,如果当前域已经得到授权,则将结果返回给页面。否则浏览器忽略此次响应。</p>
<p>网页:收到返回结果或者浏览器的错误提示。</p>
<p>总结:对于简单的跨域请求,只要服务器设置的Access-Control-Allow-Origin Header和请求来源匹配,浏览器就允许跨域。服务器端设置的Access-Control-Allow-Methods和Access-Control-Allow-Headers对简单跨域没有作用。</p>
<h2><a id="Preflighted_24"></a>带预检(Preflighted)的跨域请求流程</h2>
<p>网页:当HTTP请求出现以下两种情况时之一,浏览器认为是带预检(Preflighted)的跨域请求:</p>
<ul>
<li>
<p>请求的方法不是 GET, HEAD或者POST三种,或者是这三种,但是Content-Type不是application/x-www-form-urlencoded, multipart/form-data或text/plain中的一种。</p>
</li>
<li>
<p>请求中有自定义HTTP头部。</p>
</li>
</ul>
<p>浏览器:发现请求属于以上两种情况,向服务器发送一个OPTIONS预检请求,检测服务器端是否支持真实请求进行跨域资源访问,浏览器会在发送OPTIONS请求时会自动添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。</p>
<p>服务器:响应OPTIONS请求,会在responseHead里添加Access-Control-Allow-Methods head。这其中的method的值是服务器给的默认值,可能不同的服务器添加的值不一样。服务器还会添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。这些取决于服务器对OPTIONS请求具体如何做出响应。如果服务器对OPTIONS响应不合你的要求,你可以手动在服务器配置OPTIONS响应,以应对带预检的跨域请求。在配置服务器OPTIONS的响应时,可以添加Access-Control-Max-Age head告诉浏览器在一定时间内无需再次发送预检请求,但是如果浏览器禁用缓存则无效。</p>
<p>浏览器:接到OPTIONS的响应,比较真实请求的method是否属于返回的Access-Control-Allow-Methods head的值之一,还有origin, head也会进行比较是否匹配。如果通过,浏览器就继续向服务器发送真实请求。 否则就会报预检错误,如下几种:</p>
<p>请求来源不被options响应允许:Failed to load…Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin http://127.0.0.1:8080 is therefore not allowed access.</p>
<p>请求方法不被options响应允许:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.</p>
<p>请求中有自定义header不被options响应允许:Failed to load… Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.</p>
<p>服务器:响应真实请求,在响应头中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头,并返回数据。(如果服务器对真实请求的响应另外设置有Access-Control-Allow-Methods,它的值不会生效,个人理解是因为刚刚在服务器响应OPTIONS响应时,就已经验证过真实请求的method是属于Access-Control-Allow-Methods head的值之一)。也可以在响应真实请求时添加Access-Control-Max-Age head。</p>
<p>浏览器:接受服务器对真实请求的返回结果,返回给网页</p>
<p>网页:收到返回结果或者浏览器的错误提示。</p>
<p><strong>总结:也就是说Access-Control-Allow-Methods和Access-Control-Allow-Headers只在响应options请求时有作用,Access-Control-Allow-Origin在响应options请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。</strong></p>
<p>服务器设置OPTIONS响应一般要同时满足这些条件,一是跨域,二是有带预检的请求,三是服务器对OPTIONS响应默认值不符合要求,如果是不存在跨域情况,就不需要在服务器手动设置OPTIONS响应。</p>
<p>XMLHttpRequest支持通过withCredentials属性跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)。浏览器将携带Cookie Header的请求发送到服务器端后,如果服务器没有响应Access-Control-Allow-Credentials Header,那么浏览器会忽略掉这次响应。如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名。</p>
<h2><a id="SpringBoot_56"></a>SpringBoot设置跨域</h2>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">HandlerInterceptor</span> </span>{
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
<span class="hljs-comment">//在请求处理之前进行调用(Controller方法调用之前</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">preHandle</span><span class="hljs-params">(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)</span> <span class="hljs-keyword">throws</span> Exception </span>{
httpServletRequest.setAttribute(<span class="hljs-string">"p_real_ip"</span>, realIp);
httpServletRequest.setAttribute(<span class="hljs-string">"begin_nao_time"</span>, begin_nao_time);
httpServletResponse.addHeader(<span class="hljs-string">"Access-Control-Allow-Origin"</span>, httpServletRequest.getHeader(<span class="hljs-string">"Origin"</span>));
httpServletResponse.addHeader(<span class="hljs-string">"Access-Control-Allow-Credentials"</span>, <span class="hljs-string">"true"</span>);
httpServletResponse.addHeader(<span class="hljs-string">"Access-Control-Allow-Methods"</span>, <span class="hljs-string">"OPTIONS,HEAD,DELETE,GET,PUT,POST"</span>);
httpServletResponse.addHeader(<span class="hljs-string">"Access-Control-Allow-Headers"</span>, <span class="hljs-string">"*"</span>);
httpServletResponse.addHeader(<span class="hljs-string">"Access-Control-Max-Age"</span>, <span class="hljs-string">"3600"</span>);
}
<span class="hljs-comment">/**
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
*/</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">postHandle</span><span class="hljs-params">(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">// logger.info("完成业务处理");</span>
}
<span class="hljs-comment">/**
* 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
*/</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterCompletion</span><span class="hljs-params">(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)</span> <span class="hljs-keyword">throws</span> Exception </span>{
}
}
</code></div></pre>