前端中的 Web 安全(二):同源策略
同源策略
基本概念
如果两个 URL 的协议、端口(如果有指定的话)和主机都相同的话,则这两个 URL 是同源的。同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。
DOM 的同源策略
<script>、<img>、<iframe>、<link>的跨域请求,不受同源策略约束
XMLHTTPRequest 的同源策略
XMLHttpRequest
严格受同源策略约束,不能随意跨域请求。同样地,ajax
和axios
也受到同源策略约束。
在这种情况下,跨域的请求能够正常发出并到达服务器,但是服务器的响应会被浏览器拦截。
之所以要限制XMLHTTPRequest
,是为了用户的安全。例如当用户访问恶意网站时,执行了恶意脚本,从而向第三方网站发起请求,从而让攻击者从响应中获取用户的重要信息。有了同源限制后,在恶意网站发起的请求由于同源策略,会被浏览器所限制,因此攻击者无法拿到相应。
Web Storage 的同源策略
localStorage
和sessionStorage
都会受到同源策略限制。他们的不同之处在于:
- 同源页面共享同一个
localStorage
,而同源的页面分别拥有自己的sessionStorage
。打开多个相同的URL
的Tabs
页面,会创建各自的sessionStorage
。 - 从
A
页面跳转到同源的B
页面,或是把A
标签页复制后,新的页面会继承原来页面的sessionStorage
,之后互不影响。
跨域通信
服务器代理
同源策略的作用域是浏览器,因此,开发者可以利用服务器实现跨域通信。这种方法主要用在无法操作服务端的场景,这种情况下我们不能用CORS
或JSONP
来实现跨域,而使用服务器代理转发请求的方式可以解决该问题。
其工作流程是客户端将请求发给自己的服务器(如nginx
),服务器请求跨域信息,再返回给客户端。
即便与代理服务器不同源,也可以在代理服务器上使用CORS
等方式实现跨域通信。
JSONP
JSONP
是在CORS
出现之前使用的跨域方法,现在已经不常用了。
其原理是借助script
标签可以进行跨域通信来发起GET
请求,服务器返回json
形式的数据,js
使用callback
读取json
数据,从而实现跨域数据的传输。
CORS
CORS
机制允许Web
应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。这是HTML5
原生提供的跨域机制,该标准允许XMLHttpRequest
或Fetch API 发起跨源HTTP
请求。
在该标准下,浏览器在发起请求时,会自动在请求头中声明当前页面的源Origin
;服务器在响应头中需要通过Access-Control-Allow-Origin
设置授权允许跨域的域;之后浏览检查跨域权限后决定是否放行,若两者匹配则会放行,从而实现跨域请求。
CORS
将请求分为了简单请求和复杂请求,其中简单请求不需要发起预检请求;而复杂请求由于可能对服务器产生副作用,因此需要先发起预检请求询问服务器是否允许发起请求,收到允许后再发起实际请求。
简单请求
简单请求是指满足以下条件的请求:
- 请求方法是以下三种方法之一:
GET
、POST
、HEAD
。 - 请求头部只能是以下集合之一:
Accept
、Accept-Language
、Content-Language
、Content-Type
。 Content-Type
的值只能是以下三者之一:text/plain
、multipart/form-data
、application/x-www-http-urlencoded
。- 请求中的
XMLHttpRequestUpload
对象没有注册任何事件监听器。 - 请求中没有使用
ReadableStream
对象。
复杂请求
除了简单请求之外的请求都是复杂请求。对于复杂请求,会首先发起预检请求。预检请求以OPTIONS
形式发送,并包含两个CORS
特有的头部字段:Access-Control-Request-Method
和Access-Control-Request-Headers
。
以下是一个预检请求的例子:
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
响应:
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
在上面的例子中,对于预检请求,核心字段是Access-Control-Request-Method
和Access-Control-Request-Headers
,这携带了复杂请求的实际方法和自定义标头,Origin
字段声明了请求的源。服务器响应的四个Access-Control
字段携带了允许的源、方法、自定义头部和最大缓存时间(单位为秒)。其中Access-Control-Max-Age
表示在缓存时间内,浏览器为同一请求无需再次发起预检请求。
204
状态码表示No Content
,用于表示预检请求的成功状态,即服务器允许该请求。
附带身份凭证的请求
- 如果请求中需要携带凭证(通常是
cookie
),如果使用XMLHTTPRequest
,需要设置withCredentials
属性为true
。 - 如果服务器端响应没有携带
Access-Control-Allow-Credentials: true
,则浏览器不会把响应内容返回给请求发送者。预检请求的响应必须指定Access-Control-Allow-Credentials: true
来表明可以携带凭据进行实际的请求。 - 对于携带凭证的请求,响应中的
Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Expose-Headers
均不能为*
,必须为具体值,否则不知道要携带哪个域的cookie
。
除以上跨域方法之外,还有Document.domain
、window.postmessage
、window.name
的跨域通信方式,有兴趣大家可以自行了解,此处不做介绍了。
总结
如果以上的内容都已经学会了,那么尝试回答一下这个面试题吧:
请说出至少三种实现跨域请求的方法,并说明它们的原理和优缺点。
答案:
- 使用
JSONP
方法来实现跨域请求。这种方法是利用<script>
标签的src
属性可以加载任意域名的资源的特点,通过动态创建<script>
标签,将请求的参数拼接在src
属性中,然后在回调函数中处理返回的数据。这种方法的优点是简单易用,兼容性好,缺点是只能发送GET
请求,不能设置请求头,不安全,可能造成注入攻击。 - 使用
CORS
方法来实现跨域请求。这种方法是利用服务器端设置响应头Access-Control-Allow-Origin
来允许指定的域名进行跨域请求,浏览器会根据这个响应头来判断是否允许跨域。这种方法的优点是可以发送任意类型的请求,可以设置请求头,安全性高,缺点是需要服务器端支持,兼容性较差,可能造成性能损耗。 - 使用
Proxy
方法来实现跨域请求。这种方法是利用一个中间服务器来转发请求和响应,从而绕过浏览器的同源策略。这种方法的优点是可以发送任意类型的请求,可以设置请求头,不需要修改服务器端代码,缺点是需要额外的服务器资源,可能增加网络延迟和安全风险。
转载自:https://juejin.cn/post/7281186011711389754