HTTP的责任是去定义数据,在两台计算机相互传递信息时,HTTP规定了每段数据以什么形式表达才是能够被另外一台计算机理解。
而TCP所要规定的是数据应该怎么传输才能稳定且高效的传递与计算机之间。
(还可以再扩展)
- TCP是一个面向连接的、可靠的、基于字节流的传输层协议。
- UDP是一个面向无连接的传输层协议。
TCP为什么可靠,是因为它有三次握手来保证双方都有接受和发送数据的能力。
字节流服务:将大块数据分割为以报文段为单位的数据包进行管理
如:
-
HTTPS的工作流程
-
混合加密机制的好处
-
数字签名
-
ECDHE握手和RSA握手
-
向前安全性
这些问题都可以看到我的这篇文章:HTTPS面试问答
跨域的产生来源于现代浏览器所通用的同源策略
,所谓同源策略,是指只有在地址的:
- 协议名
- 域名
- 端口名
均一样的情况下,才允许访问相同的cookie、localStorage,以及访问页面的DOM
或是发送Ajax
请求。若在不同源的情况下访问,就称为跨域。
例如以下为同源:
http://www.example.com:8080/index.html
http://www.example.com:8080/home.html
以下为跨域:
http://www.example.com:8080/index.html
http://www3.example.com:8080/index.html
注意
但是有两种情况:http
默认的端口号为80
,https
默认端口号为443
。
所以:
http://www.example.com:80 === http://www.example.com
https://www.example.com:443 === https://www.example.com
为什么浏览器会禁止跨域?
简答:
首先,跨域只存在于浏览器端,因为我们知道浏览器的形态是很开放的,所以我们需要对它有所限制。
其次,同源策略主要是为了保证用户信息的安全,可分为两种:Ajax
同源策略和DOM
同源策略。
Ajax
同源策略主要是使得不同源的页面不能获取cookie
且不能发起Ajax
请求,这样在一定层度上防止了CSRF
攻击。
DOM
同源策略也一样,它限制了不同源页面不能获取DOM
,这样可以防止一些恶意网站在自己的网站中利用iframe
嵌入正规的网站并迷惑用户,以此来达到窃取用户信息。
深答:
-
首先,跨域只存在于浏览器端。浏览器它为
web
提供了访问入口,并且访问的方式很简单,在地址栏输入要访问的地址或者点击某个链接就可以了,正是这种开放的形态,所以我们需要对它有所限制。 -
所以同源策略它的产生主要是为了保证用户信息的安全,防止恶意的网站窃取数据。分为两种:
Ajax
同源策略与DOM
同源策略:Ajax
同源策略它主要做了这两种限制:1.不同源页面不能获取cookie
;2.不同源页面不能发起Ajax
请求。我认为它是防止CSRF
攻击的一种方式吧。因为我们知道cookie
这个东西它主要是为了解决浏览器与服务器会话状态的问题,它本质上是存储在浏览器或本地文件中一个小小的文本文件,那么它里面一般都会存储了用户的一些信息,包括隐私信息。如果没有Ajax
同源策略,恶意网站只需要一段脚本就可以获取你的cookie
,从而冒充你的身份去给其它网站发送恶意的请求。DOM
同源策略也一样,它限制了不同源页面不能获取DOM
。例如一个假的网站利用iframe
嵌套了一个银行网站mybank.com,并把宽高或者其它部分调整的和原银行网站一样,仅仅只是地址栏上的域名不同,若是用户没有注意的话就以为这个是个真的网站。如果这时候用户在里面输入了账号密码,如果没有同源策略,那么这个恶意网站就可以获取到银行网站中的DOM
,也就能拿到用户的输入内容以此来达到窃取用户信息的攻击。
同源策略它算是浏览器安全的第一层屏障吧,因为就像
CSRF
攻击,它只能限制不同源页面cookie
的获取,但是攻击者还可能通过其它的方式来达到攻击效果。(注,上面提到的
iframe
限制DOM
查询,案例如下)// HTML <iframe name="yinhang" src="www.yinhang.com"></iframe> // JS // 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom const iframe = window.frames['yinhang'] const node = iframe.document.getElementById('你输入账号密码的Input') console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
参考:
基本原理:主要就是利用 script
标签的src
属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript
函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。
执行过程:
- 前端定义一个解析函数(如:
jsonpCallback = function (res) {}
) - 通过
params
的形式包装script
标签的请求参数,并且声明执行函数(如cb=jsonpCallback
) - 后端获取到前端声明的执行函数(
jsonpCallback
),并以带上参数且调用执行函数的方式传递给前端 - 前端在
script
标签返回资源的时候就会去执行jsonpCallback
并通过回调函数的方式拿到数据了。
缺点:
- 只能进行
GET
请求
优点:
- 兼容性好,在一些古老的浏览器中都可以运行
代码实现:
(具体可以看我的这篇文章:JSONP原理及实现)
<script>
function JSONP({
url,
params = {},
callbackKey = 'cb',
callback
}) {
// 定义本地的唯一callbackId,若是没有的话则初始化为1
JSONP.callbackId = JSONP.callbackId || 1;
let callbackId = JSONP.callbackId;
// 把要执行的回调加入到JSON对象中,避免污染window
JSONP.callbacks = JSONP.callbacks || [];
JSONP.callbacks[callbackId] = callback;
// 把这个名称加入到参数中: 'cb=JSONP.callbacks[1]'
params[callbackKey] = `JSONP.callbacks[${callbackId}]`;
// 得到'id=1&cb=JSONP.callbacks[1]'
const paramString = Object.keys(params).map(key => {
return `${key}=${encodeURIComponent(params[key])}`
}).join('&')
// 创建 script 标签
const script = document.createElement('script');
script.setAttribute('src', `${url}?${paramString}`);
document.body.appendChild(script);
// id自增,保证唯一
JSONP.callbackId++;
}
JSONP({
url: 'http://localhost:8080/api/jsonps',
params: {
a: '2&b=3',
b: '4'
},
callbackKey: 'cb',
callback (res) {
console.log(res)
}
})
JSONP({
url: 'http://localhost:8080/api/jsonp',
params: {
id: 1
},
callbackKey: 'cb',
callback (res) {
console.log(res)
}
})
</script>
跨域资源共享(CORS
)是一种机制,是W3C标准。它允许浏览器向跨源服务器,发出XMLHttpRequest
或Fetch
请求。并且整个CORS
通信过程都是浏览器自动完成的,不需要用户参与。
而使用这种跨域资源共享
的前提是,浏览器必须支持这个功能,并且服务器端也必须同意这种"跨域"
请求。因此实现CORS
的关键是服务器需要服务器。通常是有以下几个配置:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Allow-Credentials
- Access-Control-Max-Age
具体可看:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
过程分析:
简单回答:
-
当我们发起跨域请求时,如果是非简单请求,浏览器会帮我们自动触发预检请求,也就是 OPTIONS 请求,用于确认目标资源是否支持跨域。如果是简单请求,则不会触发预检,直接发出正常请求。
-
浏览器会根据服务端响应的 header 自动处理剩余的请求,如果响应支持跨域,则继续发出正常请求,如果不支持,则在控制台显示错误。
详细回答:
-
浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。
-
服务器收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含
Access-Control-Allow-origin
字段,若配置过域名,则返回Access-Control-Allow-origin + 对应配置规则里的域名的方式
。 -
浏览器根据接受到的 响应头里的
Access-Control-Allow-origin
字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。
在CORS
中有简单请求
和非简单请求
,简单请求是不会触发CORS
的预检请求的,而非简单请求会。
“需预检的请求”
要求必须首先使用 OPTIONS
方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
(关于更多CORS的内容可以看我的另一篇文章:CORS原理及实现)
简单请求不会触发CORS
的预检请求,若请求满足所有下述条件,则该请求可视为“简单请求”:
简单回答:
- 只能使用
GET
、HEAD
、POST
方法。使用POST
方法向服务器发送数据时,Content-Type
只能使用application/x-www-form-urlencoded
、multipart/form-data
或text/plain
编码格式。 - 请求时不能使用自定义的
HTTP Headers
详细回答:
-
(一) 使用下列方法之一
GET
HEAD
POST
-
(二) 人为设置以下集合外的请求头
Accept
Accept-Language
Content-Language
Content-Type
(但是有限制)DPR
Downlink
Save-Data
Viewport-Width
Width
-
(三)
Content-Type
的值仅限于下面的三者之一text/plain
multipart/form-data
application/x-www-form-urlencoded
-
请求中的任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器;XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问。 -
请求中没有使用
ReadableStream
对象。
除了上面这些请求外,都是非简单请求。
若是跨域的非简单请求的话,浏览器会首先向服务器发送一个预检请求,以获知服务器是否允许该实际请求。
整个过程大概是:
- 浏览器给服务器发送一个
OPTIONS
方法的请求,该请求会携带下面两个首部字段:Access-Control-Request-Method
: 实际请求要用到的方法Access-Control-Request-Headers
: 实际请求会携带哪些首部字段
- 若是服务器接受后续请求,则这次预请求的响应体中会携带下面的一些字段:
Access-Control-Allow-Methods
: 服务器允许使用的方法Access-Control-Allow-Origin
: 服务器允许访问的域名Access-Control-Allow-Headers
: 服务器允许的首部字段Access-Control-Max-Age
: 该响应的有效时间(s),在有效时间内浏览器无需再为同一个请求发送预检请求
- 预检请求完毕之后,再发送实际请求
这里有两点要注意:
一:
Access-Control-Request-Method
没有s
Access-Control-Allow-Methods
有s
二:
关于Access-Control-Max-Age
,浏览器自身也有维护一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效,而是以最大有效时间为主。
对于跨域 XMLHttpRequest
或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest
的某个特殊标志位。
例如我们想要在跨域请求中带上cookie
,需要满足3个条件:
- web(浏览器)请求设置
withCredentials
为true
- 服务器设置首部字段
Access-Control-Allow-Credentials
为true
- 服务器的
Access-Control-Allow-Origin
不能为*
方案一:发出简单请求(这不是废话吗...)
方案二:服务端设置Access-Control-Max-Age
字段,在有效时间内浏览器无需再为同一个请求发送预检请求。但是它有局限性:只能为同一个请求缓存,无法针对整个域或者模糊匹配 URL 做缓存。
这个...也百度不到啊。咱也不懂...
这个当时面试官和我说的是,中间会经过很多的站点,比如会经过湖南,或者其它城市,由各个城市的这些站点一层一层分发下去。
CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或者网络中,在用户访问网站的时候,将其指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
当 CDN 设置了 Access-Control-Allow-Origin响应头允许跨域时,我们可以给script标签添加crossOrigin属性,从而可以使用 window.onerror 捕获 CDN 上的 js 运行时导致的详细错误信息,包括堆栈等。
如果不设置crossOrigin属性,则可能只会捕获到script error,无法获取额外的堆栈信息。
crossOrigin属性的值默认为anonymous,即不携带 cookie,如果设置为use-credentials,则会携带 cookie 和客户端证书等票据。
例如:
<script src="https://qq.com/a.js" crossOrigin="anonymous"></script>
主要是因为http1
和浏览器的原因,同一时间同一个域名最多进行6个tcp
连接。
http1
下,浏览器对一个域名的最大tcp
连接数为6,所以10张图片表现为6 + 4
。
可以使用多域名部署解决。比如5个a域名
和5个b域名
,或者6个a域名
和4个b域名
,就可以实现一瞬间全部出来了。
XSS(Cross Site Script)跨站脚本攻击。指的是攻击者向网页注入恶意的客户端代码,通过恶意的脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。
主要是分为三种:
存储型:即攻击被存储在服务端,常见的是在评论区插入攻击脚本,如果脚本被储存到服务端,那么所有看见对应评论的用户都会受到攻击。
反射型:攻击者将脚本混在URL里,服务端接收到URL将恶意代码当做参数取出并拼接在HTML里返回,浏览器解析此HTML后即执行恶意代码
DOM型:将攻击脚本写在URL中,诱导用户点击该URL,如果URL被解析,那么攻击脚本就会被运行。和前两者的差别主要在于DOM型攻击不经过服务端
- 输入检查:对输入内容中的
script
和<iframe>
等标签进行转义或者过滤 - 设置httpOnly:很多XSS攻击目标都是窃取用户cookie伪造身份认证,设置此属性可防止JS获取cookie
- 开启CSP,即开启白名单,可阻止白名单以外的资源加载和运行
CSRF攻击(Cross-site request forgery)跨站请求伪造。是一种劫持受信任用户向服务器发送非预期请求的攻击方式,通常情况下,它是攻击者借助受害者的 Cookie 骗取服务器的信任,但是它并不能拿到Cookie,也看不到Cookie的内容,它能做的就是给服务器发送请求,然后执行请求中所描述的命令,以此来改变服务器中的数据,也就是并不能窃取服务器中的数据。
防御主要有三种:
验证Token
:浏览器请求服务器时,服务器返回一个token,每个请求都需要同时带上token和cookie才会被认为是合法请求
验证Referer
:通过验证请求头的Referer来验证来源站点,但请求头很容易伪造
设置SameSite
:设置cookie的SameSite,可以让cookie不随跨站请求发出,但浏览器兼容不一
- 诱使用户点击看似无害的按钮(实则点击了透明 iframe 中的按钮).
- 监听鼠标移动事件,让危险按钮始终在鼠标下方.
- 使用 HTML5 拖拽技术执行敏感操作(例如 deploy key).
预防策略:
- 服务端添加 X-Frame-Options 响应头,这个 HTTP 响应头是为了防御用 iframe 嵌套的点击劫持攻击。 这样浏览器就会阻止嵌入网页的渲染。
- JS 判断顶层视口的域名是不是和本页面的域名一致,不一致则不允许操作,
top.location.hostname === self.location.hostname
; - 敏感操作使用更复杂的步骤(验证码、输入项目名称以删除)。
(这个来源于LuckyWinty: http://www.imooc.com/article/295400)