# 四.前端安全
# 1.ajax
Ajax 是一种用于创建快速动态网页的技术。Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面
AJAX 的工作原理:
- 0 (未初始化)还没有调用 send()方法
- 1 (载入)已调用 send()方法,正在发送请求
- 2 (载入完成)send()方法执行完毕
- 3 (交互)正在解析相应的内容
- 4 (完成)响应内容解析完成,可以在客户端调用了
优点:
- 1.减轻服务器的负担,按需取数据,最大程度的减少冗余请求
- 2.局部刷新页面,减少用户心理和实际的等待时间,带来更好的用户体验
- 3.基于 xml 标准化,并被广泛支持,不需安装插件等,进一步促进页面和数据的分离
劣势:
- 1.AJAX 大量的使用了 javascript 和 ajax 引擎,这些取决于浏览器的支持.在编写的时候考虑对浏览器的兼容性.
- 2.AJAX 只是局部刷新,所以页面的后退按钮是没有用的.
- 3.对流媒体还有移动设备的支持不是太好等
- 怎么解决呢:通过 location.hash 值来解决 ajax 过程中导致的浏览器前进后退按钮失效问题
- 怎么解决以前被人们常遇到的重复加载问题。主要比较前后的 hash 值,看其是否相等,再判断是否触发 ajax
let getDate = (method, url, content) = > new Promise((resolve, reject) = > { let xhr = new XMLHttpRequest() xhr.open(method, url) xhr.send(content) xhr.onreadystatechange = () = > { if (xhr.readyState === 4 && xhr.status === 200) { resolve(xhr.response) } } })1
2
3
4
5
6
7
8
9
10
11
# 2.跨域
- 跨域的十种方式
- 1.降级浏览器
- 2.jsonp
- 3.cors
- 4.node 中间件:proxy-middlewire
- 5.nginx:配置代理服务将后端请转发给前端
- 6.websockit:是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是 server push 技术的一种很好的实现。
- 7.postmessage:h5 跨域方法 frame.contentWindow.postMessage,e.data;e.data,e.source.postMesage
- 8.localhost.hash:c 页面动态创建 b 页面,请求里面有参数,b 页面 window.parent.parent.location.hash=location.hash,a 通过 location.hash 拿到参数
- 9.document.name:proxy 一个空的代理页面,被访问的页面动态切换 iframe 页面域名,name 没有消失,frame.contentWindow.name,window.name
- 10.document.domain:主域名相同,docuemnt.domain 强制设置主域,freame.contentWindow,window.parent
什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求吗?
因为浏览器出于安全考虑,有同源策略。也就是说,如果协议,域名或者端口有一个不同就是跨域,Ajax 请求会失败。
**那么是出于什么安全考虑才会引入这种机制呢?**其实主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登录态发起恶意请求。
也就是说,没有同源策略情况下,A 网站可以被任意其他来源的 Ajax 访问到内容。如果你当前 A 网站还存在登录态,那么对方就可以通过 Ajax 获得你的任何信息。当然跨域并不能完全阻止 CSRF。
**然后我们来考虑一个问题,请求跨域了,那么情况到底发出去没有?**请求必然是发出去了,但是浏览器拦截了响应。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会。因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器任务这不安全,所以拦截了响应。但是表单并不会获取新内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
# JSONP
JSONP 的原理很简单,就是利用<script>标签没有跨域限制的漏洞。通过<script>标签指向一个需要访问的地址并提供一个回调函数来接受数据。
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
conosle.log(data)
}
</script>
2
3
4
5
6
JSONP 使用简单且兼容性不错,但是只限于get请求
在开发总可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现
function jsonp(url, jsonpCallback, success) {
let script = document.createElement("script")
script.src = url
script.async = true
script.type = "text/javascript"
window[jsonpCallback] = function (data) {
success && success(data)
}
document.body.appendChild(script)
}
jsonp("http://xxx", "callback", function (value) {
console.log(value)
})
2
3
4
5
6
7
8
9
10
11
12
13
# CORS
CORS 需要浏览器和后端同时支持。IE8 和 9 需要通过XDomainRequest来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置Access-Control-Allow-Origin就可以开启 CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没有什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
# 简单请求
以 Ajax 为例,当满足一下条件是,会触发简单请求
- 1.使用以下方法之一:GET、HEAD、POST
- 2.
Content-Type的值仅限于下列三者之一:text/plain、multipart/form-data、application/x-www-form-urlencoded
请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器,XMLHttpRequestUplaod对象可以使用XMLHttpRequest.upload属性访问。
# 复杂请求
那么狠显然,不符合以上条件的请求肯定是复杂请求了。
对于复杂请求来说,首先会发起一个预检请求,该请求时option方法的,通过该请求来知道服务端是否运行跨域请求。
对于预检请求来说,如果你使用 Node 来设置 CORS 的话,可能会遇到一个这么一个坑。
以下以 express 框架举例:
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS")
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials"
)
next()
})
2
3
4
5
6
7
8
9
该请求会验证你的Authorization字段,没有的话就会报错。
当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果也永远是报错的。因为预检请求也会进入回调中,也会触发next方法,因为预检请求并不包含Authorization字段,所以服务端会报错。
想解决的这个问题很简单,只需要在回调中过滤option方法即可
res.statusCode = 204
res.setHeader("Content-Length", "0")
res.end()
2
3
# docuemnt.domain
该方法只能用于二级域名相同的情况下,比如a.test.com和b.test.com适用于该方式。
只需要给页面添加document.domain = 'test.com'表示二级域名都相同就可以实现跨域
# postMessage
这种通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
// 发送消息
window.parent.postMessage("message", "http//test.com")
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener("message", (event) => {
var origin = event.origin || event.originalEvent.origin
if (origin === "http://test.com") {
console.log("验证通过")
}
})
2
3
4
5
6
7
8
9
10
# 3.XSS
什么是 XSS 攻击?如何防范 XSS 攻击?什么是 CSP?
- XSS 攻击有哪些类型
- 存储型
- 反射型
- DOM 型
- 如何防御 XSS 攻击
- 输入检查
- 设置 httpOnly
- 开启 CSP
转义字符 首先,对于用户的输入应该是永远不信任的。最普通的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义。
但是对于显示富文本来说,显然不能通过上面的办法来转义所有的字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐白名单的方式。
CSP CSP 本质就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。
1.设置 HTTP Header 中的Content-Security-Policy
2.设置meta标签的方式<meta http-equiv="Content-Securiry-Policy">
这里以设置 HTTP Header 来举例
- 只允许加载本站资源
Content-Security-Policy:default-src 'self'
- 只允许加载 HTTPS 协议图片
Content-Security-Policy:img-src https://*
- 允许加载任何来源框架
Content-Security-Policy:child-src 'none'
对于这种方式来说,只要开发者配置了正确的规则,那么即使网站存在漏洞,攻击者也不能执行它的攻击代码,并且 CSP 的兼容性也不错。
# 4.CSRF
CSRF 中文名为跨站请求伪造。原理即使攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。
完成 CSRF 需要两个步骤:
- 1.登陆受信任的网站 A,在本地生成 COOKIE
- 2.在不登出 A 的情况下,或者本地 COOKIE 没有过期的情况下,访问危险网站 B。
举个例子,假如网站中有一个通过GET请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口。
<img src="http://www.domai.com/xxx?comment='attack'">
那么你也是否会想到使用POST方式提交请求是不是就没有这个问题了呢?其实并不是,使用这种方式也不是百分百安全,攻击者同样可以诱导用户进入某个页面,在页面个中通过表单提交POST请求。
如何防御
- 1、Get 请求不对数据进行修改
- 2、不让第三方网站访问到用户 Cookie
- 3、阻止第三方网站请求接口
- 4、请求时附带验证信息,比如验码或者 Token
SameSite
可以对 Cookie 设置SameSite属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。
验证 Referer
对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的
Token
服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。
# 5.点击劫持
点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过iframe嵌套的方式嵌入自己的网页中,并将iframe设置为透明,在页面中透出一个按钮诱导用户点击。
对于这种攻击方式,推荐防御的方法有两种。 X-FRAME-OPTIONS
X-FRAME-OPTIONS是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头就是为了防御用iframe嵌套的点击劫持攻击。
该响应头有三个值可选,分别是
DENY,表示页面不允许通过iframe的方式展示SAMEOPIGIN,表示页面可以在相同域名下通过iframe的方式展示ALLOW-FROM,表示页面可以在指定来源的iframe中展示
# 6.中间人攻击
中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。
通常来说不建议使用公共 Wi-Fi,因为很可能就会发生中间人攻击的情况。如果你在通信的过程中涉及到了某些敏感信息,就完全暴露给攻击方了。
当然防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。HTTPS 就可以用来防御中间人攻击,但是并不是说使用了 HTTPS 就可以高枕无忧了,因为如果你没有完全关闭 HTTP 访问的话,攻击方可以通过某些方式将 HTTPS 降级为 HTTP 从而实现中间人攻击。