xml地图|网站地图|网站标签 [设为首页] [加入收藏]
深入理解视觉格式化模型,用webgl打造自己的3D迷
分类:web前端

浅说 XSS 和 CSRF

2018/07/16 · 基础技术 · CSRF, XSS

原文出处: dwqs   

在 Web 安全领域中,XSS 和 CSRF 是最常见的攻击方式。本文将会简单介绍 XSS 和 CSRF 的攻防问题。

声明:本文的示例仅用于演示相关的攻击原理

用webgl打造自己的3D迷宫游戏

2016/09/19 · JavaScript · WebGL

原文出处: AlloyTeam   

背景:前段时间自己居然迷路了,有感而发就想到写一个可以让人迷路的小游戏,可以消(bao)遣(fu)时(she)间(hui)

没有使用threejs,就连glMatrix也没有用,纯原生webgl干,写起来还是挺累的,不过代码结构还是挺清晰的,注释也挺全的,点开全文开始迷宫之旅~

毕竟要赚一点PV,所以开头没有贴地址,现在贴地址:

github:

在线试玩:

游戏操作:鼠标控制方向,w前进,s后退,切记方向键没用啊!

迷宫本身的比较简陋,没加光和阴影啥的,挺赶的一个demo。不过这篇文章不是介绍webgl技术为主的,主要是讲解整个游戏开发的情况,let’s go~

 

1、生成2D迷宫

迷宫游戏嘛,肯定迷宫是主体。大家可以从游戏中看到,我们的迷宫分为2D迷宫和3D迷宫,首先说2D迷宫,它是3D迷宫的前提

生成迷宫有三种方式

a)深度优先

一言不合贴源码:

先看一下用深度优先法生成迷宫的图吧

图片 1

我们看下迷宫的特点,发现有一条很明显的主路,是不是能理解算法名中“深度优先”的含义了。简单介绍一下算法的原理:

图片 2

知道了原理,我们着手来制造2D迷宫~

首先得确定墙和路的关系,考虑到迷宫转化为3D之后墙立体一点,我们就不要用1px的线来模拟墙了,那样3D之后不够饱满~

这里我们设置墙的厚度为路的宽度,都是10px,然后我们的底图应该是这样子的(注:理解这幅图最为关键):

图片 3

白色部分是路,也可以理解为原理中所说的邻格,这是可以达到的

灰色部分是墙,这个墙可能会打通,也可能没有打通

黑色部分是墙,这个墙是不可能打通的!!

如果脑子没转过来就看下图,转化理解

图片 4

红线就是玩家的路径啦,其中我们看到穿过了三个黑色的矩形,这就是上面所说的灰色格,可能打通,也可能没打通,蓝色那块就是没打通的情况;而黑色部分正对应上面的黑色格,这是不可能打通的,如果把墙看成一个面(灰色蓝色部分再压缩),黑色就变成一个点,是横墙与竖墙的交点,玩家不会走交点上面走的~

好,下面就是套算法的过程啦,写的过程中我把墙的部分给省略了,全部考虑成路

JavaScript

var maxX = 18; var maxY = 13;

1
2
var maxX = 18;
var maxY = 13;

以宽度来解释,Canvas宽度390px,有18列的路(20列的墙暂时被我无视),不理解的可以对照图看一下

initNeighbor方法是获得邻格用的,注意最后有一个随机,将它的邻格打乱,这样我们在getNeighbor 中获取邻格就很方便了

JavaScript

Grid.prototype.getNeighbor = function() {     var x, y, neighbor;     this.choosed = true; // 标记当前格     for(var i = 0; i < this.neighbor.length; i++) {         x = this.neighbor[i].x;         y = this.neighbor[i].y;         neighbor = maze.grids[y][x];         if(!neighbor.choosed) { // 邻格是否标记过             neighbor.parent = this; // 选中的邻格父级为当前格             return neighbor;         }     }     if(this.parent === firstGrid) {         return 0; // 结束     } else {         return 1; // 这里是邻格都被标记过,返回父级     } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Grid.prototype.getNeighbor = function() {
    var x, y, neighbor;
    this.choosed = true; // 标记当前格
    for(var i = 0; i < this.neighbor.length; i++) {
        x = this.neighbor[i].x;
        y = this.neighbor[i].y;
        neighbor = maze.grids[y][x];
        if(!neighbor.choosed) { // 邻格是否标记过
            neighbor.parent = this; // 选中的邻格父级为当前格
            return neighbor;
        }
    }
    if(this.parent === firstGrid) {
        return 0; // 结束
    } else {
        return 1; // 这里是邻格都被标记过,返回父级
    }
};

这里比较核心,注释给的也比较全,结合前面的原理图应该很好懂

再看下maze里面的findPath方法,在这里面调用的getNeighbor方法

JavaScript

Maze.prototype.findPath = function() {     var tmp;     var curr = firstGrid; // 先确定起点     while(1) {         tmp = curr.getNeighbor(); // 获得邻格         if(tmp === 0) {             console.log('路径找寻结束');             break;         } else if(tmp === 1) { // 邻格都被标记,回到父级             curr = curr.parent;         } else { // 找到了一个没被标记的邻格,存起来             curr.children[curr.children.length] = tmp;             curr = tmp;         }     } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Maze.prototype.findPath = function() {
    var tmp;
    var curr = firstGrid; // 先确定起点
    while(1) {
        tmp = curr.getNeighbor(); // 获得邻格
        if(tmp === 0) {
            console.log('路径找寻结束');
            break;
        } else if(tmp === 1) { // 邻格都被标记,回到父级
            curr = curr.parent;
        } else { // 找到了一个没被标记的邻格,存起来
            curr.children[curr.children.length] = tmp;
            curr = tmp;
        }
    }
};

可以看到parent和children属性是不是本能的就反应起树的概念了,那不就是深度的思想么~

核心的代码讲解了,其他的画图部分就不介绍了,在drawPath方法里面,原理就是先画一个节点(一个格子),然后它的children格和它打通(前面图中灰色格子转为白色),再去画children格……

注:开头给的试玩demo用的不是深度优先算法,下面这个是深度优先生成的迷宫游戏,可以感受一下,这样与开头的有一个对比

b)广度优先(prim随机)

一言不合贴源码:

再看一下广度优先生成的迷宫图~可以和上面的对比一下

图片 5

前面说的深度优先算法挺好理解的,人类语言表达出来就是“一直走,能走多远走多远,发现不通了,死路了,再回去想想办法”。

但是,用深度优先算法在迷宫游戏中有很致命的一个缺点,就是简单,那条明显的主路让玩家不看2D地图都能轻松的绕出来(路痴退散),这明显不符合开头所说的消(bao)遣(fu)时(she)间(hui)的主题,那么正主来啦~

prim(普里姆)算法是传统迷宫游戏的标准算法,岔路多,复杂。我觉得有广度优先的思想,所有自己也称广度优先算法,正好和上一个对应上。贴原理图~

图片 6

人类语言表达出来就是“随机的方式将地图上的墙尽可能打通”,还记得这个底图么,照着这个底图我解释一下

图片 7

选择1为起点,并标记。1的邻墙有2,3,放入数组中。

此时数组[2, 3],随机选择一个,比如我们选到了2,2的对面格是4,此时4没有被标记过,打通2(将2由灰变成白色),并将4标记,并把5,6放入数组

此时数组[2, 3, 5, 6],继续随机……

结合一下源码,发现这次写法和上次的完全不同了,在深度优先中我们直接没考虑墙的存在,主体是路(白色的格子),将他们变成树的结构即可,在后面绘制部分再会考虑墙的位置

而在广度优先中,我认为主体是墙(灰色的格子),所以算法中一定要把墙的概念带上,在initNeighbor方法中路(白色格)的邻格已经是+2而不是之前的+1了,因为+1是墙(灰色格)

再看重要的getNeighbor方法

JavaScript

Grid.prototype.getNeighbor = function() {     var x, y, neighbor, ret = [];     this.choosed = true;     for(var i = 0; i < this.neighbor.length; i++) {         x = this.neighbor[i].x;         y = this.neighbor[i].y;         neighbor = maze.grids[y][x];         neighbor.wallX = this.x + (x - this.x)/2; // 重要!         neighbor.wallY = this.y + (y - this.y)/2; // 重要!         if(!neighbor.choosed) {             ret.push(neighbor);         }     }     return ret; };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Grid.prototype.getNeighbor = function() {
    var x, y, neighbor, ret = [];
    this.choosed = true;
    for(var i = 0; i < this.neighbor.length; i++) {
        x = this.neighbor[i].x;
        y = this.neighbor[i].y;
        neighbor = maze.grids[y][x];
        neighbor.wallX = this.x + (x - this.x)/2; // 重要!
        neighbor.wallY = this.y + (y - this.y)/2; // 重要!
        if(!neighbor.choosed) {
            ret.push(neighbor);
        }
    }
    return ret;
};

看起来我们获得的是邻格,但实际上我们要的是挂载在邻格上的wallX和wallY属性,所以我们可以把neighbor抽象的就看成是墙!!在下面findPath方法中就是这样用的

JavaScript

Maze.prototype.findPath = function() {     var tmp;     var curr = firstGrid;     var index;     var walls = this.walls;     tmp = curr.getNeighbor();     curr.isClear = true; // 标记     walls.push.apply(walls, tmp);     while(walls.length) {         index = (Math.random() * walls.length) >> 0; // 随机取         wall = walls[index];         if(!wall.isClear) { // 如果不是通路             wall.isClear = true;             this.path.push({                 x: wall.wallX, // 重要!                 y: wall.wallY // 重要!             });             tmp = wall.getNeighbor();             walls.push.apply(walls, tmp); // 加入更多的墙         } else {             walls.splice(index, 1); // 如果是通路了就移除         }     }     console.log('路径找寻结束', this.path); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Maze.prototype.findPath = function() {
    var tmp;
    var curr = firstGrid;
    var index;
    var walls = this.walls;
    tmp = curr.getNeighbor();
    curr.isClear = true; // 标记
    walls.push.apply(walls, tmp);
    while(walls.length) {
        index = (Math.random() * walls.length) >> 0; // 随机取
        wall = walls[index];
        if(!wall.isClear) { // 如果不是通路
            wall.isClear = true;
            this.path.push({
                x: wall.wallX, // 重要!
                y: wall.wallY // 重要!
            });
            tmp = wall.getNeighbor();
            walls.push.apply(walls, tmp); // 加入更多的墙
        } else {
            walls.splice(index, 1); // 如果是通路了就移除
        }
    }
    console.log('路径找寻结束', this.path);
};

如果感觉有点绕的话可以结合原理图再慢慢的看代码,核心理解的一点就是getNeighbor方法返回的x,y对应是路(白色格),而它的wallX,wallY对应的是墙(灰色格)

画图部分很简单

JavaScript

for(i = 0; i <= 290; i+=20) { // 隔行画横线(横墙)     ctx.fillRect(0, i, 390, 10); }   for(i = 0; i <= 390; i+=20) { // 隔行画竖线(竖墙)     ctx.fillRect(i, 0, 10, 290); }   ctx.fillStyle = 'white';   for(i = 0; i < this.path.length; i++) { // 打通墙     ctx.fillRect(10 + this.path[i].x * 10, 10 + this.path[i].y * 10, 10, 10); }

1
2
3
4
5
6
7
8
9
10
11
12
13
for(i = 0; i <= 290; i+=20) { // 隔行画横线(横墙)
    ctx.fillRect(0, i, 390, 10);
}
 
for(i = 0; i <= 390; i+=20) { // 隔行画竖线(竖墙)
    ctx.fillRect(i, 0, 10, 290);
}
 
ctx.fillStyle = 'white';
 
for(i = 0; i < this.path.length; i++) { // 打通墙
    ctx.fillRect(10 + this.path[i].x * 10, 10 + this.path[i].y * 10, 10, 10);
}

c)递归分割法

这个实在是超级简单,原理简单,算法简单,我就不介绍啦。一来这个生成的迷宫也超级简单,一般不用于传统迷宫游戏;二来后面还有很多要介绍的,不浪费口水在这了

 

2、生成3D迷宫

此时我们已经有一个2D迷宫,我们可以将其看成是俯视图,下面就是将其转化为3D顶点信息

注:这篇文章不负责介绍webgl!!我也尽量避开webgl知识,通俗一点的介绍给大家~

将2D转3D,首先非常重要的一点就是坐标系的转化

2D的坐标系是这样的

图片 8

3D的坐标系是这样的

图片 9

感觉到蛋疼就对了~后面考虑到摄像机近平面的碰撞计算还得蛋碎呢~

其实这个坐标转换并不难,首先我们先通过2D迷宫获得墙面的信息(黑色部分)

下面这段代码是获得横墙信息的

JavaScript

function getRowWall() {     var i = 0;     var j = 0;     var x1, x2;     console.log('getRowWall');     for(; i < height; i += 10) {         rowWall[i] = [];         j = 0;         while(j < width) {             if(isBlack(j, i)) {                 x1 = j; // 记录横墙开始点                                  j += 10;                 while(isBlack(j, i) && j < width) {                     j += 10;                 }                   x2 = j; // 记录横墙结束点                 if((x2 - x1) > 10) { // 这步很关键!!                     rowWall[i].push({                         x1: 2 * (x1 / width) - 1,                         x2: 2 * (x2 / width) - 1                     });                 }             }               j += 10;         }     }       // console.log(rowWall); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function getRowWall() {
    var i = 0;
    var j = 0;
    var x1, x2;
    console.log('getRowWall');
    for(; i < height; i += 10) {
        rowWall[i] = [];
        j = 0;
        while(j < width) {
            if(isBlack(j, i)) {
                x1 = j; // 记录横墙开始点
                
                j += 10;
                while(isBlack(j, i) && j < width) {
                    j += 10;
                }
 
                x2 = j; // 记录横墙结束点
                if((x2 - x1) > 10) { // 这步很关键!!
                    rowWall[i].push({
                        x1: 2 * (x1 / width) - 1,
                        x2: 2 * (x2 / width) - 1
                    });
                }
            }
 
            j += 10;
        }
    }
 
    // console.log(rowWall);
}

结果会得到一个数组,注意一下注释中很关键的一步,为什么要大于10

下面两张图给你答案

图片 10

图片 11

总结就是小于等于10px的横墙,那它的本体一定是竖墙,10px也是那一行正好看到的,我们就将他们过滤掉了

得到竖墙信息同理,源码可见,我就不贴出来了

下面这段代码是2D坐标转化为顶点信息

JavaScript

// k1和k2算作Z轴 for(i = 0; i < rowWall.length; i += 10) { // rowWall.length     item = rowWall[i];     while((tmp = item.pop())) {         k1 = (2 * i / height) - 1;         k2 = (2 * (i + 10) / height) - 1;         po_data.push.apply(po_data, [             tmp.x1*120+0.01, -1.09, k1*120, // 左下             tmp.x2*120+0.01, -1.09, k1*120, // 右下             tmp.x2*120+0.01, 0.2, k1*120, // 右上             tmp.x1*120+0.01, 0.2, k1*120, // 左上               tmp.x2*120+0.01, -1.09, k1*120,             tmp.x2*120+0.01, -1.09, k2*120,             tmp.x2*120+0.01, 0.2, k2*120,             tmp.x2*120+0.01, 0.2, k1*120,               tmp.x1*120+0.01, -1.09, k2*120,             tmp.x2*120+0.01, -1.09, k2*120,             tmp.x2*120+0.01, 0.2, k2*120,             tmp.x1*120+0.01, 0.2, k2*120,               tmp.x1*120+0.01, -1.09, k1*120,             tmp.x1*120+0.01, -1.09, k2*120,             tmp.x1*120+0.01, 0.2, k2*120,             tmp.x1*120+0.01, 0.2, k1*120,               tmp.x1*120+0.01, 0.2, k1*120,             tmp.x2*120+0.01, 0.2, k1*120,             tmp.x2*120+0.01, 0.2, k2*120,             tmp.x1*120+0.01, 0.2, k1*120         ]);     } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// k1和k2算作Z轴
for(i = 0; i < rowWall.length; i += 10) { // rowWall.length
    item = rowWall[i];
    while((tmp = item.pop())) {
        k1 = (2 * i / height) - 1;
        k2 = (2 * (i + 10) / height) - 1;
        po_data.push.apply(po_data, [
            tmp.x1*120+0.01, -1.09, k1*120, // 左下
            tmp.x2*120+0.01, -1.09, k1*120, // 右下
            tmp.x2*120+0.01, 0.2, k1*120, // 右上
            tmp.x1*120+0.01, 0.2, k1*120, // 左上
 
            tmp.x2*120+0.01, -1.09, k1*120,
            tmp.x2*120+0.01, -1.09, k2*120,
            tmp.x2*120+0.01, 0.2, k2*120,
            tmp.x2*120+0.01, 0.2, k1*120,
 
            tmp.x1*120+0.01, -1.09, k2*120,
            tmp.x2*120+0.01, -1.09, k2*120,
            tmp.x2*120+0.01, 0.2, k2*120,
            tmp.x1*120+0.01, 0.2, k2*120,
 
            tmp.x1*120+0.01, -1.09, k1*120,
            tmp.x1*120+0.01, -1.09, k2*120,
            tmp.x1*120+0.01, 0.2, k2*120,
            tmp.x1*120+0.01, 0.2, k1*120,
 
            tmp.x1*120+0.01, 0.2, k1*120,
            tmp.x2*120+0.01, 0.2, k1*120,
            tmp.x2*120+0.01, 0.2, k2*120,
            tmp.x1*120+0.01, 0.2, k1*120
        ]);
    }
}

乘以120是我3D空间中X轴和Z轴各放大了120倍,没有写在模型变换矩阵里面,Y轴的方法在模型变化矩阵中,不过那不重要。

数组中三个单位为一点,四个点为一个面,五个面为3D迷宫中一堵墙(底面的不管)

后面是webgl里面常规操作,各种矩阵、绑定buffer、绑定texture等等balabala,原生webgl写起来是比较累,无视了光和阴影还要写这么多T_T

 

3、摄像机碰撞检测

如果说前面的代码写着很累看着累,那这里的就更累了……

摄像机是什么?在3D中摄像机就是玩家的视角,就是通过鼠标和w,s来移动的webgl可视区,那么在2D中摄像机映射为什么呢?

2D中摄像机就是红色的那个圈圈的右点,如图!

图片 12

那么大的圈圈只是方便看而已……

碰撞检测的作用是防止出现透视现象,透视现象如下图所示:

图片 13

要介绍透视现象出现的原因,就得先了解一下视锥体,如图:

图片 14

看到近平面了吗,当物体穿过近平面,就会出现透视现象了

图片 15

我们游戏中近平面距离是0.1,所以可能看成围绕原点有一个矩形,只要让矩形碰到不边,那就不会出现透视现象

矩形的宽度我设置为2,设大了一些,也没必要让玩家贴墙贴的那么近……

我们通过调用摄像机的move方法触发Role.prototype.update方法

JavaScript

move: function(e){     // 只考虑x和z轴移动,cx,cy是转换为2D的方向     cx = Math.sin(-this.rot) * e;     cy = Math.cos(-this.rot) * e;         this.x += cx;     this.z += cy;       ret = role.check(-this.x/120, this.z/242, -cx, cy); // 后两个参数代表方向       if(ret.x === 0) {         this.x -= cx;     } else {         role.x = ret.x;     }       if(ret.y === 0) {         this.z -= cy;     } else {         role.y = ret.y;     }       role.update(); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
move: function(e){
    // 只考虑x和z轴移动,cx,cy是转换为2D的方向
    cx = Math.sin(-this.rot) * e;
    cy = Math.cos(-this.rot) * e;
 
 
    this.x += cx;
    this.z += cy;
 
    ret = role.check(-this.x/120, this.z/242, -cx, cy); // 后两个参数代表方向
 
    if(ret.x === 0) {
        this.x -= cx;
    } else {
        role.x = ret.x;
    }
 
    if(ret.y === 0) {
        this.z -= cy;
    } else {
        role.y = ret.y;
    }
 
    role.update();
}

而update方法里面更新x0,x2,y0,y2就是对应那四个点,这四个点在check方法里面用到,check通过则移动摄像机,否则不移动

摄像机与墙的整体检测在Role.prototype.isWall中,注意这里有两个参数,cx和cy,这个是方向,确切的说是将要移动的方向,然后我们根据方向,只会从这四个点中取三个来判断会不会有碰撞

图片 16

每个点的检测通过Role.prototype.pointCheck方法,通过像素来判断的,发现是黑色值(rgb中的r为0)那么就认为撞上了,会在2D中标记黄色。如果你贴着墙走,就会发现黑色的墙都被染成黄色啦~

 

结语:

写累死,这还是在把webgl里面知识点大部分丢掉的情况下。迷宫整体比较简单,就两张贴图,地面也很简陋,最近需求比较多,很忙,没太多时间去美化。有兴趣的同学可以做一款属于自己棒棒的迷宫游戏~

感兴趣有疑问的可以留言一起交流~

4 赞 2 收藏 评论

图片 17

深入理解视觉格式化模型

2016/08/31 · CSS · 1 评论 · CSS, 绝对定位

原文出处: 腾讯 ISUX - 他大舅   

“理论不懂就实践,实践不会就学理论”,非常赞同bluedavy的这句话。实践过程中经常会遇到某个属性的使用,浏览器渲染效果与预期效果不符,虽然通过死记硬背能避免或巧妙应用这种效果,但总感心虚发慌、毫无自信,因为不知晓背后的原理。这时就不要再用“就是这样的”的借口来搪塞自己,我们需要重新认识它。

XSS

XSS,即 Cross Site Script,中译是跨站脚本攻击;其原本缩写是 CSS,但为了和层叠样式表(Cascading Style Sheet)有所区分,因而在安全领域叫做 XSS。

XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。

XSS攻击可以分为3类:反射型(非持久型)、存储型(持久型)、基于DOM。

实践与现象

绝对定位是一种常用的定位方式,也经常会看到一些使用技巧,轻松搞定一些不太容易实现的效果。现介绍两个绝对定位的使用技巧:

1. 绝对定位元素,水平方向(top和bottom)或和垂直方向(left和right)的定位值不设置时,其位置受其前面的兄弟元素影响,如同其在常规流中的位置。如下例所示:

  • 元素A,C绝对定位,不设置top,bottom值;
  • 元素B处于常规流中;

结果是:元素C的位置受元素B的影响,跟随在元素B的下方。

See the Pen absolute-position by wenjul (@wenjul) on CodePen.

这种看似毫无用处的技巧,却能帮助我们解决一些项目实际问题。我们总希望我们的布局是自适应的,即不依赖与所处环境,当环境改变时,仍能完美工作。下面这个实例要求蓝色购买按钮水平居中,其后跟随一个链接。为了达到自适应布局,我们不能假设父级容器宽度固定,也不能假设蓝色按钮的文案固定,所以链接元素的位置也是根据上下文环境改变的。这种情况下,我们就可以对链接设置绝对定位,并且不用设置left 和right 值,两者的间距通过margin值实现,即可轻松达到预期效果。(当然,通过嵌套的方式也可实现,但不是最优解)

See the Pen center-el-followed-by-link by wenjul (@wenjul) on CodePen.

另一个案例是用以实现下拉菜单,下拉菜单通常由触发按钮和下拉列表组成,下拉列表的位置位于触发按钮的下方。同样,由于触发按钮的高度是可能变化的,那么下拉列表与触发按钮顶端的绝对距离是不固定的,使用单位px是无法达到自适应的,通常的技巧是设置top:100%,其实利用我们上面提到的技巧,对top和bottom不设置值也是可以实现的。

See the Pen dropdown menu by wenjul (@wenjul) on CodePen.

2. 绝对定位结合margin实现垂直居中

很多设计都可以抽象为“一个元素相对于父级(或包含块)在垂直方向或水平方向上居中对齐”的模式,根据实际情况又可分为该元素的尺寸未知和已知两种情况。这是个经久不衰的话题,实现方式也多种多样,这里我们讨论的是“尺寸已知元素在垂直方向上的居中对齐”问题。你可能看到过下面这种实现方式,绝对定位元素的4个值均为0,margin在垂直方向上也设置为了auto(支持IE8+)。一般为了水平居中会在水平方向上设置auto,为什么这种情况下,在垂直方向上设置auto,会导致垂直居中的效果呢?

See the Pen center-middle by wenjul (@wenjul) on CodePen.

反射型

反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。

看一个示例。我先准备一个如下的静态页:

图片 18

恶意链接的地址指向了 localhost:8001/?q=111&p=222。然后,我再启一个简单的 Node 服务处理恶意链接的请求:

JavaScript

const http = require('http'); function handleReequest(req, res) { res.setHeader('Access-Control-Allow-Origin', '*'); res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'}); res.write('<script>alert("反射型 XSS 攻击")</script>'); res.end(); } const server = new http.Server(); server.listen(8001, '127.0.0.1'); server.on('request', handleReequest);

1
2
3
4
5
6
7
8
9
10
11
const http = require('http');
function handleReequest(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
    res.write('<script>alert("反射型 XSS 攻击")</script>');
    res.end();
}
 
const server = new http.Server();
server.listen(8001, '127.0.0.1');
server.on('request', handleReequest);

当用户点击恶意链接时,页面跳转到攻击者预先准备的页面,会发现在攻击者的页面执行了 js 脚本:

图片 19

这样就产生了反射型 XSS 攻击。攻击者可以注入任意的恶意脚本进行攻击,可能注入恶作剧脚本,或者注入能获取用户隐私数据(如cookie)的脚本,这取决于攻击者的目的。

规范与原理

为了解决这个疑虑,我重新学习了CSS 2.1规范中的 9 Visual formatting model 和 10 Visual formatting model details,现将相关章节译录于此。

这两章讲解了视觉格式化模型:用户代理在视觉媒体上如何处理文档树。在视觉格式化模型中,文档树中的每个元素根据框模型(box modal)生成0或多个框。这些框的布局由以下因素决定:

  • 框尺寸和类型
  • 定位方案(常规流、浮动和绝对定位)
  • 文档树中元素之间的关系
  • 外部信息(比如viewport尺寸、图像的固有尺寸等)

9.1.2 Containing blocks(包含块)

CSS 2.1中,许多框的位置和尺寸的计算是相对于一个矩形框的边缘,这个矩形框称为包含块。通常情况下,生成框是后代框的包含块(generated boxes act as containing blocks for descendant boxes;),称之为一个框为其后代创建了包含块。短语“一个框的包含块”指的是“这个框存在其中的包含块”,而非它生成的框。 每个框会相对于其包含块赋予位置,但它并不囿于包含块,可能会溢出(overflow)。包含块的尺寸计算细节在第10章有详细介绍。

存储型

存储型 XSS 会把用户输入的数据 “存储” 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。

比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。

举一个示例。

先准备一个输入页面:

<input type="text" id="input"> <button id="btn">Submit</button> <script> const input = document.getElementById('input'); const btn = document.getElementById('btn'); let val; input.addEventListener('change', (e) => { val = e.target.value; }, false); btn.addEventListener('click', (e) => { fetch('', { method: 'POST', body: val }); }, false); </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<input type="text" id="input">
<button id="btn">Submit</button>  
 
<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');
 
    let val;
    
    input.addEventListener('change', (e) => {
        val = e.target.value;
    }, false);
 
    btn.addEventListener('click', (e) => {
        fetch('http://localhost:8001/save', {
            method: 'POST',
            body: val
        });
    }, false);
</script>

启动一个 Node 服务监听 save 请求。为了简化,用一个变量来保存用户的输入:

const http = require('http'); let userInput = ''; function handleReequest(req, res) { const method = req.method; res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type') if (method === 'POST' && req.url === '/save') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { if (body) { userInput = body; } res.end(); }); } else { res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'}); res.write(userInput); res.end(); } } const server = new http.Server(); server.listen(8001, '127.0.0.1'); server.on('request', handleReequest);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const http = require('http');
 
let userInput = '';
 
function handleReequest(req, res) {
    const method = req.method;
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
    
    if (method === 'POST' && req.url === '/save') {
        let body = '';
        req.on('data', chunk => {
            body += chunk;
        });
 
        req.on('end', () => {
            if (body) {
                userInput = body;
            }
            res.end();
        });
    } else {
        res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
        res.write(userInput);
        res.end();
    }
}
 
const server = new http.Server();
server.listen(8001, '127.0.0.1');
 
server.on('request', handleReequest);

当用户点击提交按钮将输入信息提交到服务端时,服务端通过 userInput 变量保存了输入内容。当用户通过 http://localhost:8001/${id} 访问时,服务端会返回与 id 对应的内容(本示例简化了处理)。如果用户输入了恶意脚本内容,则其他用户访问该内容时,恶意脚本就会在浏览器端执行:

图片 20

9.2 Controlling box generation(控制框生成)

本节描述了CSS 2.1中可生成的框类型。一个框的类型部分地影响其在视觉格式化模型中的行为。

9.2.1 Block-level elements and block boxes

处在块格式化环境(BFC,block formatting context)中的框称之为块级框(block-level box)。每个块级元素生成一个包含后代框和生成的内容的主体块级框,同时这个框与定位方案密切相关。有些块级元素除了生成主体框外,还会生成一个附加框,如’list-item’元素。附加框相对于主体框定位。 表框(table boxes)和替换元素(replaced elements)外,块级框同时也是块容器框(block container box)。块容器框要么仅包含块级框,要么建立一个行内格式化环境(IFC,inline formatting context),即仅包含行内级框。并非所有的块容器框都是块级框:非替换行内块(inline blocks)和非替换表格单元格都是块容器,但不是块级框。既是块级框也是块容器的框称为块框(block box)。 “块级框”、“块容器框”和“块框”这三个术语有时被简称为块(block)

9.2.3 Run-in boxes(插入型框)

CSS Level 3的CSS basic box model中定义。 run-in框的行为如下:

  1. 如果run-in框包含一个块框,那么run-in框变为块框。
  2. 如果run-in框的后继兄弟元素为块框(非浮动,非绝对定位),那么run-in框变为该块框的第一个行内框。run-in不能插入本身为run-in的块中,也不能插入块中已有run-in的块中。
  3. 否则,run-in框变为块框。

浏览器支持:IE8+(chrome不支持,难道是太鸡肋?)IE下查看效果

图片 21

9.3.2 Box offsets: ‘top’, ‘right’, ‘bottom’, ‘left’

  • (绝对、固定)定位元素会生成一个定位框(positioned box),根据top,right,bottom,left布局。
  • 初始值为auto,非0。(文章开头的问题中未设置四值,等同设置为auto
  • 对于绝对定位元素,四值指定的是元素margin边与包含块的边之间的偏移量。对于相对定位元素,四值指定的是相对于自身框边的偏移量。

基于DOM

基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。

看如下代码:

<h2>XSS: </h2> <input type="text" id="input"> <button id="btn">Submit</button> <div id="div"></div> <script> const input = document.getElementById('input'); const btn = document.getElementById('btn'); const div = document.getElementById('div'); let val; input.addEventListener('change', (e) => { val = e.target.value; }, false); btn.addEventListener('click', () => { div.innerHTML = `<a href=${val}>testLink</a>` }, false); </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<h2>XSS: </h2>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');
    const div = document.getElementById('div');
 
    let val;
    
    input.addEventListener('change', (e) => {
        val = e.target.value;
    }, false);
 
    btn.addEventListener('click', () => {
        div.innerHTML = `<a href=${val}>testLink</a>`
    }, false);
</script>

点击 Submit 按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容:

'' onclick=alert(/xss/)

1
2
'' onclick=alert(/xss/)
 

用户提交之后,页面代码就变成了:

<a href onlick="alert(/xss/)">testLink</a>

1
<a href onlick="alert(/xss/)">testLink</a>

此时,用户点击生成的链接,就会执行对应的脚本:

图片 22

9.6 Absolute positioning

  • 从常规流中完全抽离,对其后继兄弟元素无影响。
  • 固定定位是绝对定位的特例,它的包含块是viewport。

XSS 攻击的防范

现在主流的浏览器内置了防范 XSS 的措施,例如 CSP。但对于开发者来说,也应该寻找可靠的解决方案来防止 XSS 攻击。

9.7 Relationships between ‘display’, ‘position’, and ‘float’

这三个属性影响了框的生成和布局,相互影响如下:

  1. 如果’display’值为’none’,同时不设置’position’和’float’,那么该元素不生成框。
  2. 否则,如果’positon’值为’absolute’或’fixed’,即框为绝对定位,’float’的计算值为’none’,并且’display’根据下表设置。那么该框的位置由’top’,’right’,’bottom’,’left’和框的包含块决定。
  3. 否则,如果’float’的值不为’none’,那么该框会浮动,’display’根据下表设置。
  4. 否则,如果该元素为根元素,’display’根据下表设置。
  5. 否则,剩余的’display’属性值与指定值相同。
指定值 计算值
inline-table table
inline, table-row-group, table-column, table-column-group, table-header-group, table-footer-group, table-row, table-cell, table-caption, inline-block block
others same as specified

本文由澳门新葡亰手机版发布于web前端,转载请注明出处:深入理解视觉格式化模型,用webgl打造自己的3D迷

上一篇:了解一些额外知识,的人都理解错了HTTP中GET与 下一篇:CSS镂空图片transition过渡初加载背景色块问题解决
猜你喜欢
热门排行
精彩图文