一 HTML页面重绘和重排
1.1 浏览器的运行机制
1,构建DOM树(parse),渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树(Content Tree/DOM tree);
2,构建渲染树(construct),解析对应的css样式文件信息(包括js生成的样式和外部css文件),而这些文件信息以及HTML中可见的指令(如),构建渲染树(Render Tree/Frame Tree),Render Tree中的每个node都有自己的style,而且Render Tree不包含隐藏的节点(比如display: none的节点,还有head节点),因为这些节点不会用于呈现;
3,布局渲染树(reflow/layout),从根节点递归调用,计算每一个元素的大小,位置等,给出每个节点所因该在屏幕上出现的精确坐标;
4,绘制渲染树(paint/repaint),遍历渲染,使用UI层来绘制每个节点。
1.2 重绘和重排
重绘
当盒子的位置,大小以及其他颜色属性,例如颜色,字体大小等都确定下来之后,浏览器便会把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。
重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
触发重绘的条件:改变元素的外观属性,如color, background-color等
注意:table及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍的时间,这就是我们尽量避免使用table布局页面的原因之一;
重排(重构/回流/reflow)
当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流。每个页面至少需要一次回流,就是在页面第一次加载的时候;
重绘和重排的关系
在重排的时候,浏览器会使渲染树中受到影响的部分失效,并重新构建这部分渲染树,完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。所以,重排必定引发重绘,重绘不一定会引发重排;
触发重排的条件
- 页面渲染初始化(无法避免)
- 添加或者删除可见的DOM元素;
- 元素位置的改变,或者使用动画;
- 元素尺寸的改变-大小,外边距,边框;
- 浏览器窗口尺寸的变化(resize事件发生时);
- 填充内容的改变,比如文本的改变或图片大小改变而引起的计算宽度和高度的改变;
- 读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Heigth, width/height, getComputedStyle(), currentStyle(IE) )
重绘重排的代价:耗时,浏览器卡顿
1.3 优化
针对重绘重排,可以做以下优化:
- 浏览器自己的优化:浏览器会维护一个队列,把所有会引起回流,重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理,这样就会让多次的回流,重绘变成一次回流重绘;
- 减少重绘和重排就是减少对渲染树的操作,可以合并多次的DOM和样式的修改,并减少对style样式的请求;
1,直接改变元素的classname
2,display:none;先设置元素为display:none;然后在进行页面布局等操作;设置完成后将元素设置为display:block;这样就只引发两次重绘和重排;
3,使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘;
4,将需要多次重绘的元素,position属性设为absolute或fixed,元素脱离了文档流,它的变化不会影响到其他元素;
5,如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性加入到document;
代码实现:
var fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
var li = document.createElement('li');
li.innerHTML = 'list' + i;
fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
二 http缓存控制
2.1 缓存介绍
web缓存大致可以分为:数据库缓存,服务器端缓存(代理服务器缓存,cdn缓存),浏览器缓存;
浏览器缓存也包含很多内容:http缓存,indexDB,cookie,localstorage等等。这里我们只讨论http缓存相关内容;
缓存术语
缓存命中率:从缓存中得到数据的请求数与所有请求数的比率,理想状态是越高越好。
过期内容:超过设置的有效时间,被标记为陈旧的内容,通常过期内容不能用于回复客户端的请求,必须重新向服务器请求新的内容或者验证缓存的内容是否仍然准备。
验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
失效:失效就是把内容从缓存中移除,当内容发生改变时就必须移除失效的内容。
浏览器缓存主要是http协议定义的缓存机制。HTML meta标签,例如:
<META HTTP-EQUIV="Progma" CONTENT="no-store">
含义是让浏览器不缓存当前页面。但是代理服务器不能解析HTML内容,一般应用广泛的是用HTTP头信息控制缓存。
2.2 浏览器请求流程图
浏览器第一次请求
浏览器第二次请求
-
浏览器在加载资源时,现根据这个资源的一些http header判断它是否强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源 不会发请求到服务器,比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件缓存配置命中了强缓存,浏览器就直接从缓存中 加载这个css,连请求都不会发送到网页所在服务器。
-
当强缓存过期的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存, 如果协商缓存命中,服务器会将这个请求返还,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器 就会从自己的缓存中去加载这个资源。
-
强缓存和协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源;区别是强缓存不发请求到服务器,协商缓存会发请求到服务器。
-
当协商缓存也没有命中的时候,浏览器直接从服务器中加载资源数据。那么代表该请求的缓存失败,返回200,重新返回资源和缓存标识。再存入浏览器缓存中,生效则返回304,继续使用缓存;
2.3 浏览器缓存分类
浏览器缓存分为强缓存和协商缓存,浏览器加载一个页面的简单流程如下:
- 浏览器先根据这个资源的http头信息来判断是否命中缓存,如果命中则直接加载缓存中的资源,并不会将请求资源发送到服务器端。(强缓存)
- 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器,服务器来判断浏览器本地缓存是否失效。若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源。(协商缓存)
- 如果未命中缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存。(新的请求)
2.3.1 强缓存
命中强缓存时,浏览器并不会将请求发送给服务器。在Chrome的开发者工具中看到http的返回码是200,但是在size列表会显示为(from cache)
强缓存是利用http的返回头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间;
Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体时间点。也就是说Expires=max-age + 请求时间,需要和Last-modified结合使用,但在上面我们提到过,cache-control的优先级更高。Expires是Web服务器响应消息头字段,在响应http请求到告诉浏览器在过期时间前浏览器可以从浏览器缓存取数据,而无需再次请求。
该字段会返回一个时间,比如Expires: Thu, 31 Dec 2037 23:59:59 GMT。这个时间代表着这个资源的失效时间,也就是说在2037年12月31日23点59分59秒之前都是有效的,即命中缓存。这种方式都有一个明显的缺点,由于失效时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端时间偏差变大以后,就会导致缓存混乱,于是发展出了Cache-Control.
Cache-Control
Cache-Control是一个相对时间,例如Cache-Control: 3600,代表资源的有效期是3600秒。由于是相对时间,并且都是与客户端时间比较,所有服务器与客户端时间偏差也不会导致问题。
Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。
Cache-Control可以由多个字段组合而成,主要有以下几个值;
- max-age指定一个时间长度,在这个时间段内缓存是有效的,单位是s, 例如设置Cache-Control:max-age=31536000, 也就是说缓存有效期为(31536000/24/60*60)天,第一次访问这个资源的时候,服务器也返回了Expires字段,并且过期时间是一年后。
在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取; - s-maxage 同max-age,覆盖max-age,Expires,但仅适用于共享缓存,在私有缓存中被忽略。
- public 表明响应可以被任何对象(发送请求的客户端,代理服务器等等)缓存;
- private 表明响应只能被单个用户(可能是操作系统用户,浏览器用户)缓存,是非共享的,不能被代理服务器缓存。
- no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器,不是字面意思上的不缓存。
- no-store 禁止缓存,每次请求都要向服务器重新获取数据。
- must-revalidate指定如果页面是过期的,则去服务器进行获取,这个指令并不常用,就不做过多讨论了。
2.3.2 协商缓存
若未命中强缓存,则浏览器会将请求发送至服务器,服务器根据http头信息中的Last-Modify/If-Modify-Since或Etag/If-None-Match来判断是否命中协商缓存,如果命中,则http返回码为304,浏览器从缓存中加载资源。
Last-Modify/if-Modify-Since
浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-Modify是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu, 31, Dec 2037 23:59:59 GMT。当浏览器再次请求该资源的时候,服务器返回的header中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。如果命中缓存,则返回http304,并且不会返回资源内容,并且不会返回Last-Modify。由于对比的服务器端时间,所以客户端与服务端时间差距不会导致问题。但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致)。于是出现了ETag/If-None-Match。
ETag/If-None-Match
与Last-Modify/If-Modify-Since不同的是,ETag/If-None-Match返回的是一个校验码(Etag: entity tag)。Etag可以保证每一个资源是唯一的,资源变化都会导致Etag变化。Etag值的变更则说明资源状态已被修改。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。
Etag扩展说明
我们对ETag寄予厚望,希望它对于每一个URL生成唯一的值,资源变化时ETag也发生变化。神秘的ETag是如何生成的呢?以Apache为例,ETag生成靠以下几种因子;
- 文件的i-node编号,此i-node非彼iNode,是Linux/unix用来标识文件的编号。是的,识别文件用的不是文件名。使用命令ls -l可以看到。
- 文件最后修改时间
- 文件大小
生成ETag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。所以,理论上ETag也是会重复的,只是概率小到可以忽略。
既生Last-Modify何生Etag
你可能会觉得Last-Modify已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要E tag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modify比较难解决的问题;
-
Last-Modify标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
-
如果某些文件会被定期生成,当有时内容并没有任何变化,但是Last-Modify却改变了,导致文件没法使用缓存
-
有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形;
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modify与Etag是可以一起使用的,服务器会优先验证Etag, 一致的情况下,才会继续对比Last-Modify,最后才决定是否返回304;
2.3.3 H5离线缓存manifest
HTML5提出的一个新的特性,离线缓存,通过离线存储,我们可以通过把需要离线存储在本地的文件列表写入manifest 配置文件中,这样即使在离线的情况下,用户也可以看见网页。
查看
在application --- application cache里面可以看见
使用
-
在需要离线缓存存储的页面上,加上manifest = "cache.manifest"
<!DOCTYPE html> <html manifest = "cache.manifest"> ... </html>
-
在根目录新建文件cache.maifest并写上对应代码
CACHE MANIFEST #v0.11 CACHE: js/app.js css/style.css NETWORK: resourse/logo.png FALLBACK: / /offline.html
离线存储的manifest一般由三个部分组成:
-
CACHE:表示需要离线存储的资源文件,由于包含manifest文件的页面将被自动离线存储,所以不需要把页面自身也列出来;会在当前浏览器存上;
-
NETWORK:表示在它下面列出来的资源只有在在线的情况下才能访问,它们不会被离线存储,所以在离线情况下无法使用这些 资源。不过,如果在CACHE 和 NETWORK中有一个相同的资源,那么这个资源还是会被离线存储,也就是说CACHE的优先级更高;
-
FALLBACK:表示如果访问第一个资源失败,那么就使用第二个资源来替换他,比如上面这个文件表示的就是如果访问根目录下任何一个资源失败了; 就去访问offline.html.
三 ES6装饰器的使用
3.1 什么是装饰器
装饰器(Decorator)是一种与类(class)相关的语法,用来修改类和类方法与属性。实现一些代码的复用;进入代码就会执行;
在当前的浏览器或Node.js版本中,尚不支持装饰器,需要使用Babel插件@babel/plugin-proposal-decorators
来将它转换为浏览器或Node.js中可识别的代码。
装饰类
@frozen
class Foo {
// 装饰method方法
@configurable(false)
method() {}
// 装饰handle() 方法
@throttle(500)
handle() {}
}
3.2 修饰类
@decorator
class A {}
// 等同于
class A {}
A = decorator(A);
// decorator是一个函数,相当于调用它,给A类可以加上一些其他代码
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
// 为它加上了静态属性isTestable, testable函数的参数target就是MyTestableClass类本身。
3.3 修饰类方法
装饰器
function readonly(target, name, descriptor) {
// descriptor对象原来的值如下
// {
// configurable: fasle, // 能否使用delete,能否需改属性特性,或能否修改访问器属性,
// false为不可重新定义,默认值为true
// enumerable: false, // 对象属性是否可以通过for-in循环,false为不可循环,默认值为true
// writtable: false, // 对象属性是否可以修改,false为不可修改,默认值为true
// value: 'stoney' // 对象属性的默认值
// }
descriptor.writable = false;
return descriptor;
}
class Person {
@readonly
abc() {
console.l.og('我是person的abc函数')
}
@readonly
xx = 123;
}
readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);
-
装饰器第一个参数是 类的原型对象,上例是 Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身)
-
第二个参数是 所要装饰的属性名
-
第三个参数是 该属性的描述对象
3.4 函数方法的装饰
装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
另一方面,也可以采用高阶函数的形式直接执行
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
四 项目api请求模块封装
接口请求一般是异步的,可以返回promise,网络请求URL的公共部分可以单独配置到网络请求内部,可以借助第三方库axios快速封装网络请求模块;
import axios from "axios";
import constant from "../constant";
import reactNavigationHelper from "./reactNavigationHelper";
import commonToast from "./commonToast";
// 配置请求URL的公共部分及超时时间
const commonHttp = axios.create({
baseURL: constant.baseUrl,
timeout: 10 * 100
});
commonHttp.interceptors.response.use(
function(response) {
return response;
},
function(error) {
// 针对所有接口统一处理登录过期的问题
if(error.response.status === 401) {
commonToast.show("登录过期");
reactNavigationHelper.navigate("Login");
}
return Promise.reject(error);
}
);
export default commonHttp;
五 树和数组的相互构建
5.1 给定原始数据,期待生成一个树形结构(treeData)
const originData = [
{
id: 0,
parentId: null,
name: "生物"
},
{
id: 1,
parentId: 0,
name: "动物"
},
{
id: 2,
parentId: 0,
name: "植物"
},
{
id: 3,
parentId: 0,
name: "微生物"
},
{
id: 4,
parentId: 1,
name: "哺乳动物"
},
{
id: 5,
parentId: 1,
name: "卵生动物"
},
{
id: 6,
parentId: 2,
name: "种子植物"
},
{
id: 7,
parentId: 2,
name: "蕨类植物"
},
{
id: 8,
parentId: 4,
name: "猫"
},
{
id: 9,
parentId: 4,
name: "狗"
},
{
id: 10,
parentId: 4,
name: "猩猩"
},
{
id: 11,
parentId: 5,
name: "蟒蛇"
},
{
id: 12,
parentId: 5,
name: "麻雀"
},
];
代码实现
const arrayToTree = (arr) => {
if(!Array.isArray(arr) || arr.length < 1) return null;
const [root] = arr.filter(item => item.parentId === null);
const addChildren = (node, dataList) => {
const children = dataList
.filter(item => item.parentId === node.id)
.map(item => addChildren(item, dataList));
return {...node, children};
};
return addChildren(root, arr);
};
5.2 上面生成的树形结构再还原为数组
const treetoArray = (node) => {
const nodeToArray = (node, arr) => {
const {children, ...item} = node;
arr.push(item);
children.forEach(child => nodeToArray(child, arr));
return arr;
};
return nodeToArray(node, []);
};
六 JS给对象部署iterator接口并封装成函数
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。即如果想要用for...of...遍历你的数据结构, 就得部署Iterator接口。原生提供这个接口的数据结构有:数组,类数组对象(如argument),Map和Set结构;
对象没有部署Iterator接口,下面实现了在对象上部署Iterator接口的代码实现;
let makeIterator = obj => {
obj[Symbol.iterator] = () => {
let keys = Object.keys(obj);
let index = 0;
return {
next() {
return index > keys.length - 1 ?
{ value: undefined, done: true } : {
value: obj[keys[index++]], done: false
}
}
}
}
}
let obj = {a: 'a', b: 'b', c: 'c'}
makeIterator(obj);
let y = obj[Symbol.iterator]();
console.log(y.next());
console.log(y.next());
console.log(y.next());
console.log(y.next());
for (let i of obj) {
console.log(i);
}
七 XSS CSRF以及如何防范
7.1 xss
在web安全领域中,xss和csrf是最常见的攻击方式;
xss,即Cross Site Script,跨站脚本攻击;XSS攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者用户隐私数据的一种攻击方式。
攻击者对客户端网页注入的恶意脚本一般包括javascript,有时也会包含html和flash。有很多方式进行xss攻击,但它们的共同点为:将一些隐私数据像cookie等送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意的操作。
xss攻击可以分为三类:反射型(非持久型),存储型(持久型),基于DOM型。
反射型(Reflected XSS) 发出请求时,xss代码出现在URL中,作为输入提交到服务器端,服务器端解析后响应,xss代码随响应内容一起传回浏览器,最后浏览器执行xss代码。这个过程像一次反射,所以叫反射性 xss; 例如浏览器获取订单列表,黑客插入一些恶意代码,服务器端额外返回一些内容,把你的密码明文返回;
存储型 Stored XSS,和Reflected XSS的差别就在于,具有攻击性的脚本被保存到了服务器端 (数据库,内存,文件系统)并且可以被普通用户完整的从服务器取得并执行,从而获得了在网络上传播的能力。例如浏览器端用户保存一些评论信息,黑客插入一些恶意脚本,经过服务器存储在数据库里面,那么其他用户也可以看到这些评论;
DOM型(DOM-based or local XSS), 即基于DOM或本地的XSS攻击:其实是一种特殊类型的反射型xss,它是基于DOM文档对象模型的一种漏洞。可以通过DOM来动态修改页面内容,从客户端获取DOM中的数据并在本地执行。基于这个特性,就可以利用js脚本来实现xss漏洞的利用;浏览器访问服务器,黑客加恶意脚本,例如页面上有公司logo,恶意脚本把logo干掉,换成别的;
防御措施:
1,输入过滤,避免xss的方法之一主要就是将用户输入的内容进行过滤,对所有用户提交的内容进行可靠的输入验证,包括对URL,查询关键字,post数据等,仅接受指定长度范围内,采用适当格式,采用所预期的字符的内容提交,对其他的一律过滤(客户端和服务器都要);
2,输出转义;例如在html标签之间插入不可信数据的时候,首先要做的就是对不可信数据进行html entity编码;
function escape(str) {
str = str.replace(/&/g, '&')
str = str.replace(/</g, '<')
str = str.replace(/>/g, '>')
str = str.replace(/"/g, '&quto;')
str = str.replace(/'/g, ''')
str = str.replace(/`/g, '`')
str = str.replace(/\//g, '/')
return str
}
3,使用httpOnly Cookie
将重要的cookie标记为httponly, 这样的话当浏览器向web服务器发起请求的时候就会带上cookie字段,但是js脚本中却不能访问这个cookie,这样就避免了xss攻击利用javascript的document.cookie获取cookie;
现代文本开发框架例如vue.js,react.js等,在设计的时候就考虑了xss攻击对html插值进行了更进一步的抽象,过滤和转义,我们只要熟练正确的使用它们,就可以在大部分情况下避免xss攻击;
7.2 csrf
csrf,即跨站请求伪造(Cross-site request forgery),顾名思义,是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过cookie等方式辨识用户身份(包括使用服务器端session的网站,因为session ID也是大多数保存在cookie里面的),再予以授权的。所以要伪造用户的正常操作,最好的方法是通过xss或链接欺骗等途径,再让用户在本机(即拥有身份cookie的浏览器端)发起用户所不知道的请求。
你可以这样理解:攻击者盗用了你的身份,以你的名义发起恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件,发消息,盗取你的帐号,添加系统管理员,甚至于购买产品,虚拟货币转账等。如下:其中web A为存在csrf漏洞的网站,web B为攻击者构建的恶意网站,User C为web A网站的合法用户。
CSRF攻击原理过程如下:
1,用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2,在用户信息通过验证后,网站A产生cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3,用户未退出网站A之前,在同一浏览器中,打开了一个TAB页访问网站B;
4,网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5,浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知道的情况下携带了cookie信息,向网站A发出请求,网站A并不知道该请求其实是由B发起的,所以会根据用户C的cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行;
举例:
受害者Bob在银行有一笔存款,通过对银行的网站发送请求:
http://bank.example/withdraw?account=bob&amount=100000&for=bob2
可以使Bob把100000的存款转到bob2的帐号下。通常情况下,该请求发送到网站后,服务器会先验证请求是否来自一个合法的session,并且该session的用户Bob已经成功登录。
黑客Mallory自己在银行也有帐号,也知道上文中的URL可以把钱进行转账操作,Mallory可以自己发送一个请求给银行:
http://bank.example/withdraw?account=bob&amount=100000&for=Mallory
但是这个请求来自Mallory而非Bob,他不能通过安全验证,因此该请求不会起作用。这时,Mallory想到CSRF的攻击方式,他先自己做一个网站,在网站中放入如下代码:
src="http://bank.example/withdraw?account=bob&amount=100000&for=Mallory",
并且通过广告等诱使Bob来访问它的网站。当Bob访问该网站时,上述URL就会从Bob的浏览器发向银行,而这个请求会附带Bob浏览器中的cookie一起发向银行服务器。大多数情况下,该请求会失败,因为他要求Bob的认证信息。但是,如果Bob当时恰恰刚访问他的银行不久后,他的浏览器与银行网站之间的session尚未过期,浏览器的cookie之中含有Bob的认证信息。这时,这个URL请求就会得到响应,钱将从Bob的帐号转移到Mallory的帐号,而Bob毫不知情。等以后Bob发现钱少了,去银行查询,也只能查到确实有一个来自于他本人的合法请求转移了现金,没有任何被攻击的痕迹。
防范CSRF
跨站请求伪造的防范,主要在服务器端做的:
1,服务器中验证请求头 refer字段
2,加token
3,加验证码