前面写的《IT 技术人员的自我修养》, 没想到几天内收到了不少良好的反馈, 在此感谢大家的关注. 往后会不定时分享一些技术, 管理领域的工作经验总结与感悟, 欢迎大家持续关注, 交流. 最近被问及一个跨域的问题, 包括之前面试时发现很多面试者对跨域及其处理也是一知半解, 故本文对该问题进行了梳理总结, 以供参考.
1. 什么是跨域
理解什么是跨域, 就要先了解一个叫 "同源策略" 的东西, 什么是 "同源策略"? 这是浏览器为了网站访问安全, 对来自不同源的请求做一些必要的访问限制的一种策略. 那什么叫 "同源" 呢? 我们知道, 一个 http 请求地址一般包含四部分: 协议:// 域名: 端口 / 路径, 所谓同源, 就是前面三者, 即协议, 域名, 端口都一样. 举例说明, 假如我们有一个地址 http://blog.jboost.cn/docker-1.html, 来看以下地址是否与它同源
地址 | 是否同源 | 说明 |
---|---|---|
https://blog.jboost.cn/docker-1.html | 不同源 | 协议不同,一个 http,一个 https |
http://www.jboost.cn/docker-1.html | 不同源 | 域名不同 |
http://blog.jboost.cn:8080/docker-1.html | 不同源 | 端口不同,一个是默认端口 80,一个是 8080 |
http://blog.jboost.cn/docker-2.html | 同源 | 虽然路径不同,但协议、域名、端口(默认 80)都相同 |
那么浏览器对不同源的请求做了哪些访问限制呢? 共有三种限制
对 Cookie,LocalStorage, 以及 IndexDB(浏览器提供的类 NoSQL 的一个本地数据库)的访问
对 DOM 的访问
Ajax 请求
而跨域就是要打破这种访问限制, 对不同源的资源请求也能顺利进行, 最常见的就是 Ajax 请求, 比如前后端分离架构中, 两者服务域名不同, 前端通过 Ajax 直接访问服务端接口, 就会存在跨域问题.
2. 为什么会存在跨域
前面说 "同源策略" 时已经提到, 浏览器是为了网站的访问安全, 才设置了跨域这道屏障. 那么前面所说的三种限制, 分别都是如何来保障网站安全的.
对本地存储 Cookie,LocalStorage,IndexDB 的访问限制
我们系统的登录凭证一般是通过在 Cookie 中设置 SESSIONID(如针对浏览器表单请求)或直接返回 token(如针对 REST 请求)的形式返回给客户端的, 比如 Tomcat 是通过在 Cookie 中设置名为 JSESSIONID 的属性来保存的, 而一般 REST 请求的 token 前端会存储于 LocalStorage 中, 如果不存在访问限制, 则你访问的其它网站可能就会获取到这些凭证, 然后伪造你的身份来发起非法请求, 这就太不安全了.
对 DOM 的访问限制
如果不对 DOM 进行访问限制, 那么其它网站, 尤其一些钓鱼网站, 就可以通过 <iframe> 的形式拿到你访问网站的 DOM, 进而获取到你输入的一些敏感信息, 比如用户名, 密码...
对 Ajax 请求的限制
同源策略规定, Ajax 请求只能发给同源的网址, 否则就会报错. 至于为什么要限制, 一方面是避免 1 中所提到伪造非法请求, 另一方面我理解是 Ajax 过于灵活, 如果不做限制, 可能网站的接口资源就会被其它网站随意使用, 就像你的私有物品被别人招呼都不打任意拿去用一样.
总之, 同源策略是浏览器提供的最基本的一种安全保障机制或约定.
3. 怎么实现跨域访问
我们平常遇到的跨域问题基本都出现在 Ajax 请求的场景, 一般而言, 可以通过代理, CORS,JSONP 等方式来解决跨域问题.
3.1 代理
既然 "同源策略" 是浏览器端的机制, 那我们就可以绕开浏览器, 最常见的做法就是使用代理, 如 Nginx, 比如我们前端项目的域名是 http://blog.jboost.cn, 服务端接口域名是 http://api.jboost.cn, 我们在 Nginx 中提供如下配置
- server{
- # 端口
- listen 80;
- # 域名
- server_name blog.jboost.cn;
- # 所有 http://blog.jboost.cn/api/xxx 请求都会被转发到 http://api.jboost.cn/api/xxx
- location ^~ /API {
- proxy_pass http://api.jboost.cn;
- }
- }
则前端通过 Ajax 请求服务端接口 http://api.jboost.cn/api/xxx 都可以改为通过 http://blog.jboost.cn/api/xxx 来访问, 从而避免不同源的跨域问题.
3.2 CORS
CORS 是 Cross-Origin Resource Sharing 的简写, 即跨域资源共享, CORS 需要服务端与浏览器同时支持, 目前所有浏览器 (除 IE10 以下) 都支持 CORS, 因此, 实现 CORS, 主要就是服务端的工作了. 例如在 Spring Boot 中, 我们可通过如下配置注册一个 CorsFilter 的过滤器来实现跨域支持.
- @Configuration
- @ConditionalOnClass({Servlet.class, CorsFilter.class})
- public class CORSAutoConfiguration {
- @Bean
- @ConditionalOnMissingBean(name = "corsFilterRegistrationBean")
- public FilterRegistrationBean corsFilterRegistrationBean() {
- UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.applyPermitDefaultValues();
- corsConfiguration.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
- corsConfiguration.addExposedHeader(HttpHeaders.DATE);
- corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
- CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
- filterRegistrationBean.setFilter(corsFilter);
- filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
- filterRegistrationBean.addUrlPatterns("/*");
- return filterRegistrationBean;
- }
- }
其实质就是在响应消息的 Header 中添加几个属性, 主要有
Access-Control-Allow-Origin 必需, 表示允许跨域的请求源, 可以是具体的域名, 也可以是 * , 表示任意域名
Access-Control-Allow-Methods 必需, 表示允许跨域访问的 HTTP 方法, 如 GET,POST,PUT,DELETE 等, 可以是 * , 表示所有
Access-Control-Allow-Headers 如果请求包括 Access-Control-Request-Headers 头信息, 则必需, 表示服务器支持的所有头信息字段
3.3 JSONP
JSONP 是利用浏览器对 HTML 一些标签 (如 <script>, <img > 等) 的 src 属性不具有同源策略限制的特性实现的, 如前端添加
<script type="text/javascript" src="http://api.jboost.cn/hello?name=jboost&callback=jsonpCallback"/>
并且定义 JS 方法 jsonpCallback. 服务端接口返回内容需要是 JS 方法 jsonpCallback 的调用格式, 如 jsonpCallback({"name":"jboost"}), 这样在 jsonpCallback 方法中就可以获取服务端实际返回的结果数据 {"name":"jboost"} 了.
JSONP 方式的局限性也很明显, 一是只支持 GET 请求 -- 你没见过哪些 < script>, <img > 标签是 POST 请求吧, 二是需要对服务端返回数据格式做处理.
4. 总结
三种跨域支持的实现, 代理方式最简单, 对客户端, 服务端都不具有侵入性, 但如果需要支持的请求源比较多, 或者是与第三方对接的话, 代理方式就不太适用了. CORS 相对来说是一种标准的处理方式, 并且通过过滤器的方式对业务代码也没有任何侵入性. 而 JSONP 方式局限性较大, 只支持 GET, 并且需要服务端做返回数据格式的支持. 可针对具体情况选择适用的方式.
来源: https://www.cnblogs.com/spec-dog/p/11277943.html