同源策略和跨域

什么是跨域问题?

为什么会出现跨域问题?

因为浏览器的同源策略(同源指的是:协议+域名+端口相同)。

同源策略是浏览器的安全策略,限制某一个域的网站或者网站加载的脚本如何与另一个域的资源进行交互。

也就是浏览器为了保护当前的网站用户的信息安全,限制了当前网站与其他域的服务的交互。所谓的限制并非阻止,而是通过一些约定好的方式,决定是否进行这次跨域交互。

为什么浏览器要有同源策略

没有同源策略限制的接口请求

先补充一个context:存储在浏览器上的cookie会在浏览器向同一服务器再次发起请求时被自动携带,并发往server。

如果没有同源策略的限制,在下面这种csrf攻击的情况下,你将会受到很大的影响

  • 你使用用户名和密码登录了www.yinhang.com网站,并且获取到了cookic并存放在了本地。
  • 之后给这个银行server发送的www.yinhang.com/getMoney等请求都会自动的携带这个cookie作为用户凭证给server发送请求
  • 突然你某一个地方诱导你点击了一个坏网站 www.yinghangbad.com,这时候你点进去,这个网站偷偷的给银行的server发送了请求www.yinhang.com/getMoney获取你的账户信息,显而易见,这个请求和网站的域名并不相同,如果没有同源策略的限制,这个请求会被轻松的携带你的cookie从银行server成功的获取money信息

没有同源策略限制的Dom查询

某一个坏的网站利用<iframe>将银行网站嵌入到自己的网站中,想要通过自己的写的js文件,拿到嵌入的银行网站的HTML username和password所在的input框的value。

如果没有浏览器的同源策略:只能access同源网站上的DOM元素, 那么坏网站上的JS就可以access银行网站上的DOM元素,这将会造成用户名和密码泄露。


由此可见浏览器的同源策略确实能在某种程度上保护了你的网站。
同源策略导致了:

  • 非同源的请求无法通信,真实流程是:

    • 浏览器发现当前的网站正在给一个非同源的服务发送请求,浏览器在请求的header中加上一个Origin标识来自的源,帮助service判断是否需要相应来自这个域的请求
    • 当请求的response回到浏览器,浏览器会首先检查header中是否带有Access-Control-Allow-Origin, 并且检查该字段的value中是否包含当前网站所在的域(也就是由server决定是否允许这个域的请求),只有包含,浏览器才会将请求的response返回给网站。
  • 非同源的DOM或者JS无法访问

  • 非同源的cookie、LocalStorage、IndexDB无法读写

但是DOM的同源策略可以理解,对于网站上的所有请求总不能因为不同源而被全部阻断,那么如何允许跨域的请求呢?

会受到同源策略影响的资源

以下是会加载外部资源的HTML标签:

  • script标签对JS文件的请求
  • link标签对css文件的请求
  • Img对image的请求
  • 通过 @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  • 通过 <video> 和 <audio> 播放的多媒体资源。
  • 通过 <object>、 <embed> 和 <applet> 嵌入的插件。
  • 通过 <iframe> 载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。

会受到同源策略影响的资源

  • font
  • 所有ajax请求

在浏览器中, <script> 、<img>、<iframe>、<link>等标签都可以跨域加载,而不受浏览器的同源策略的限制

如何解决因同源策略带来的跨域请求失败

一般我们都是采用CORS(跨域资源共享)解决此类问题的

跨域资源共享是一种基于HTTP header的机制。这个机制的原理:

  • 某一个ajax请求从一个网站发出,请求的server是一个和当前网站域名不一致的server,也就是说这是一个跨域请求。

  • 请求刚开始被fire的时候,经过浏览器,浏览器检测到这是一个跨域请求,就会自动在这个request的header中加上: Origin字段,value就是当前网站protocol://host:port 比如https://developer.mozilla.org
    这个字段是不能够被修改的

  • 请求到达Server之后, 服务器可以检查这个origin,并决定是否给这个origin提供服务,提供怎样的服务。

  • 如果服务器最终决定给这个请求提供服务,那么服务器就需要让浏览器知道,因此需要在response header中带上

Access-Control-Allow-Origin value中就是服务器能够服务的域名

Access-Control-Allow-Origin: <origin> | *

注意这里的origin是能是一个域名不能是多个

  • 浏览器接受到来自response,会首先check header中是否包含Access-Control-Allow-Origin,并查看当前的域是否在value中。如果是,浏览器会把response成功返回给网站,如果不是,那么就会报错。

ps: 当然还包含很多其他的header,一般Access-Controll-XXX-XXX都是和跨域相关的header

当然以上只是CORS的一种场景,正常情况根据请求的不同,跨域机制也不同:

简单请求

通常请求类型是:

  • GET
  • HEAD
  • POST

并且header中没有特殊的内容,只有常见的accept、content-type等

且content-type只能是application/x-www-form-urlencoded text/plain

符合上述条件的请求一般就是简单请求,简单请求的跨域机制就符合上面的步骤,只要有origin以及access-controllXX

注意如果请求的header中content-type是application/json这类请求一定不是简单请求。

非简单请求(复杂请求)

较为复杂的不在上面简单请求类型的请求就是复杂请求,浏览器在发出这类请求之前会发出一个预检请求

预检请求
是当你要发出一个跨域请求时,浏览器会自动率先发出一个OPTION类型的请求,带上Origin, 询问服务器是否能够允许接下来的实际复杂请求,预检请求会在请求头部带上实际请求的相关信息:

Access-Control-Request-Method: POST
// 实际请求的method
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
// 实际请求中即将有的header

服务器会根据预检请求带来的实际请求的信息,决定是否允许接下来的实际请求,如果允许,会在response的header中携带

Access-Control-Allow-Methods: GET, POST, PUT
// 必须字段,标明当前服务器可以允许的来自此域的所有请求method
Access-Control-Allow-Headers: X-Custom-Header
// 标明当前服务器可以允许的来自此域的所有请求能携带的header字段
Access-Control-Allow-Credentials: true
// 后续会提到
Access-Control-Max-Age: 1728000
// 可选字段,标明本次预检请求的有效时间,在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

浏览器会根据预检请求的response决定是否发送实际请求给服务器。

之后的实际请求会按照上面简单请求的流程,request会被浏览器携带origin, response必须有Access-Controll-Allow-XXX

带credentials的请求

通常HTTP请求可以通过cookie 或者 Authorization header给服务器发送身份凭证。

但是对于跨域的请求,在没有特殊设置的前提下,浏览器不会发送身份凭证信息给服务器,也就是说任何cookie或者authorization都不会自动的携带到某一个请求上。这是浏览器的同源策略之一。

那么如何将身份信息携带在请求中呢?

需要双重设置:

  • 需要在XMLHttpRequest或者fetch上设置
    request.withCredentials = true, 浏览器才会在请求上携带身份信息

  • 服务器接收到身份信息处理完毕之后,必须在response中携带Access-Control-Allow-Credentials: true 否则浏览器不会把响应内容返回给请求的发送者。

  • 对于这种携带身份信息的请求,它response的Access-Control-Allow-Origin 的value不能是*,只能是origin

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容