postgres对Array的操作

操作符 描述 示例 结果
sf sfs sdf sdfd
= 相等 SELECT ARRAY[1.1,2.1,3.1]::int[] = ARRAY[1,2,3]; true
<> 不等于 select ARRAY[1,2,3] <> ARRAY[1,2,4]; true
< 小于 select ARRAY[1,2,3] < ARRAY[1,2,4]; t
> 大于 select ARRAY[1,4,3] > ARRAY[1,2,4]; t
<= 小于或等于 select ARRAY[1,2,3] <= ARRAY[1,2,3]; t
>= 大于或等于 select ARRAY[1,4,3] >= ARRAY[1,4,3]; t
@> 包含 select ARRAY[1,4,3] @> ARRAY[3,1]; t
<@ 包含于 select ARRAY[2,7] <@ ARRAY[1,7,4,2,6]; t
&& 重叠(是否有相同元素) select ARRAY[1,4,3] && ARRAY[2,1]; t
|| 数组与数组连接 select ARRAY[1,2,3]||ARRAY[4,5,6]; {1,2,3,4,5,6}
|| 数组与数组连接 select ARRAY[1,2,3]||ARRAY[[4,5,6],[7,8,9]]; {1,2,3},{4,5,6},{7,8,9}
|| 元素与数组连接 select 3 || ARRAY[4,5,6]; {3,4,5,6}
|| 数组与元素连接 select ARRAY[4,5,6] || 7; {4,5,6,7}

postgresql数组操作符与函数

postgres to_number(text,text2) //text2 = 999999.99 为保留两位有效数据,不然将不识别小数点,只认识数字类型
select cast(“extension”#>>’{lat}’ as double precision) as lat, cast(“extension”#>>’{lng}’ as numeric) as lng from ads where id=6;

Docker上安装的postgres的备份以及恢复

在服务器上使用docker安装postgres

先安装docker,安装之后将postgres从docker hub包管理器pull当前到服务器

1
2
//拉取postgres版本号为10.5
docker pull postgres:10.5

然后使用docker安装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
docker run --name postgres10 \
//设置镜像名字叫做 postgres10
-e POSTGRES_PASSWORD=123456 \
//设置postgres服务器初始密码
-p 5434:5433 \
//将docker内部端口5433映射绑定到服务器端口5434
-v /data/docker/postgres10/data/:/var/lib/postgresql/data \
//将docker镜像内部文件夹/var/lib/postgresql/data映射绑定到服务器文件夹/data/docker/postgres10/data/
-v /data/docker/postgres10/buckup:/var/lib/postgresql/buckup \
-v /data/docker/postgres10/postgresql.conf:/etc/postgresql/postgresql.conf \
//将docker镜像内部文件/etc/postgresql/postgresql.conf映射绑定到服务器文件夹/data/docker/postgres10/postgresql.conf
//此文件需提前创建不然docker会认为其实一个文件夹
-d postgres:10.5 \
-c 'port=5433' \
//设置postgres默认端口号为5433此值可在postgresql.conf上配置
//同理所有config_file文件【postgresql.conf】内部可配置的值都可通过docker -c 选项进行制定
-c 'config_file=/etc/postgresql/postgresql.conf'
//指定 postgres使用的配置文件为 /etc/postgresql/postgresql.conf 即上文映射的服务器文件/data/docker/postgres10/postgresql.conf

postgresql.conf文件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
listen_addresses = '*'
//监听地址,默认*为当前服务器地址 []docker内部绑定容器需要使用*号 不能使用127 127 是相对于母机的如果跨docker没用 docker内部指向不一样
// docker使用需要开启
// sysctl net.ipv4.ip_forward //命令测试是否打开
// net.ipv4.ip_forward = 1

port = 5433
//postgres使用端口号默认5432
max_connections = 1000
//最大连接数
shared_buffers = 1024MB
max_wal_senders = 8
wal_keep_segments = 1024
max_worker_processes = 16

wal_level = replica
//置保存操作日志的具体程度级别,postgres10之后有选项三个【minimal,replica,logical】设,要设置WAL归档至少要设置为replica
archive_mode = on
//是否启用WAL归档 , 设置为on
archive_command ='cp %p /var/lib/postgresql/buckup/archive/%f > /var/lib/postgresql/buckup/archive/test.log 2>&1'
//postgres进行系统归档时候可配置的执行脚本【将当前归档文件拷贝到/var/lib/postgresql/buckup/archive/文件夹】
//【%p 表示将要归档的WAL文件的包含完整路径信息的文件名,用 %f 代表不包含路径信息的WAL文件的文件名】
//[注] 该shell脚本的路劲为docker内部路径,非服务器文件路径

wal_level和archive_mode参数更改配置之后需要重启服务器生效
archive_command参数不必重启服务器只需要重新获取配置即可

进入数据库方式

1
2
3
4
5
6
7
8
[root@dev ~]# docker exec -it postgres10 bash //进入镜像
root@964a3684307c:/# su postgres //换用户未postgres
postgres@964a3684307c:/$ psql -p 5433 //进入数据库指定端口为5433

进入数据库必须指定端口不然可能报错
connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
未找到pgsql默认socket连接id

重新加载部分配置方式

1
$postgres:  SELECT pg_reload_conf();

此时数据库已经开启了增量备份方式,数据库的归档文件会被拷贝到服务器文件夹/data/docker/postgres10/buckup/archive即docker的postgres镜像中的/var/lib/postgresql/buckup/archive/文件夹中

测试定点恢复

想要增加备份还需要进行基础备份。
pg10之后推荐使用pg_basebackup进行基础备份

1
$ pg_basebackup -Ft -Pv -Xf -U postgres -p 5433 -D /var/lib/postgresql/buckup/pg_base_buckup

之后就可以看到服务器文件夹/data/docker/postgres10/buckup/pg_base_buckup即镜像中的/var/lib/postgresql/buckup/pg_base_buckup文件夹中可以看到基础备份的压缩文件

由于WAL文件是写满16MB才会进行归档,测试阶段可能写入会非常少,可以在执行完 基础备份之后,手动进行一次WAL切换

1
postgres=# select pg_switch_wal();

此时已经手动归档一次,可在归档文件夹查看到相关文件

首先,我们创建一张测试表:

1
2
3
4
5
6
CREATE TABLE test_restore(
id SERIAL PRIMARY KEY,
ival INT NOT NULL DEFAULT 0,
description TEXT,
created_time TIMESTAMPTZ NOT NULL DEFAULT now()
);

初始化一些测试数据作为基础数据,如下所示:
1
2
3
INSERT INTO test_restore (ival) VALUES (1);
INSERT INTO test_restore (ival) VALUES (1);
INSERT INTO test_restore (ival) VALUES (1);

1
select pg_switch_wal();

下来,创建一个还原点
1
select pg_create_restore_point('halfmy-1127');

接下来我们对数据做一些变更, 我们删除test_restore的所有数据:
1
delete from test_restore;

然后关闭postgres服务,然后将基础备份解压到postgres的data文件夹
【即postgres的基础文件夹】【即本文中的postgres安装文件夹】【即本文中的服务器文件夹/data/docker/postgres10/data/】【即本文中的docker镜像中的/var/lib/postgresql/data文件夹】
1
2
3
docker stop postgres10
由于此时的docker镜像已经关闭无法进入镜像进行文件夹操作所以使用镜像相对无服务器的映射文件夹
tar -xvf /data/docker/postgres10/buckup/pg_base_buckup.base.tar -C /data/docker/postgres10/data

进入/data/docker/postgres10/data文件夹创建recovery.conf文件
1
vim recovery.conf

1
2
restore_command = 'cp /var/lib/postgresql/buckup/archive/%f %p'
recovery_target_name = 'halfmy-1127'

然后进行重启docker的postgres镜像
1
docker restart postgres

对数据库进行数据查看发先已经恢复
到data文件夹中查看发现 recovery.conf已经变成recovery.done
则已经完成 数据库的增量备份以及恢复过程
如经历上述过程中发现无法解决报错等请查阅以下文档
如何在PostgreSQL中实现增量备份
Postgresql备份与数据恢复
PostgreSQL 最佳实践 - 在线增量备份与任意时间点恢复
PostgreSQL 10.1 手册

SSH连接主机Github

使用 ssh-keygen -t rsa -C “xxxx@xxx.com“ 创建github秘钥文件
在github上创建了keys后使用
ssh -T git@github.com
测试连接报错 Permission denied(publickey)。
此时无法知道 报错具体原因,需要使用
ssh -T -v git@github.com
命令查看具体错误原因

发现
debug1: Will attempt key: /Users/wqiong/.ssh/id_rsa
debug1: Will attempt key: /Users/wqiong/.ssh/id_dsa
debug1: Will attempt key: /Users/wqiong/.ssh/id_ecdsa
debug1: Will attempt key: /Users/wqiong/.ssh/id_ed25519
debug1: Will attempt key: /Users/wqiong/.ssh/id_xmss

git一直在查找id_rsa,id_dsa等文件,
而我在创建ssh秘钥文件时候创建了文件名,并未被识别,所以将之前创建的秘钥文件名更改为id_rsa,以及id_rsa.pub即可。
再次使用
ssh -T git@github.com
发现通过

MySql和postgres的事务以及锁

显示事务级别
show transaction_isolation;

更改数据库事无级别
set transaction ISOLATION LEVEL 【read committed】【repeatable READ】【serializable】;
一、隔离问题及隔离级别

1、隔离问题:
a、脏读:一个事务读到另一个事务没有提交的数据

b、不可重复读:一个事务读到另一个事务已提交的数据(update、delete:一个事务重新执行查询,发现数据因被另一个已经提交的事务 update 操作而改变)

c、虚读(幻读):一个事务读到另一个事务已提交的数据(insert:一个事务重新执行查询,发现查询结果的集合因另一个已提交事务的 insert 操作而改变)

2、隔离级别

a、read uncommitted:读未提交。存在3个问题

b、read committed:读已提交。解决脏读,存在2个问题可以

postgreSQL、oracle默认:可以看到已提交的;第二个事务等第一事务提交后提交

(注:select看到的是select开始之前数据库的快照)

c、repeatable read:可重复读。解决:脏读、不可重复读,存在1个问题。

mysql:show global variables like 'tx_isolation';

mysql默认:看不到已提交的;第二个事务等第一个事务提交后回滚(exception:concurrent update)

d、serializable :串行化。都解决,单事务(如redis的单线程)。

(注:select看到的是事务开始之前数据库的快照)

3、SQL标准允许的:4种隔离级别只定义了哪种现象不能发生,但是没有定义哪种现象一定发生

隔离级别 脏读 幻读 不可重复性读取
读未提交 可能 可能 可能
读已提交 不可能 可能 可能
可重复读 不可能 可能 不可能
可串行读 不可能 不可能 不可能
二、操作

1、linux客户端进入:./psql -h localhost -p 5444 -U rimu rimu 进去后输入密码(./psql -h ip- p port -U 用户名 数据库名)

linux客户端退出:quit

2、开启事务:begin;

3、提交事务:commit;

4、查看postgreSQL默认的隔离级别:show default_transaction_isolation;

5、查看postgreSQL当前的隔离级别:show transaction_isolation;

6、修改当前事务的隔离级别:set transaction isolation level 四种级别;

7、修改当前回话的隔离级别:set default_transaction_isolation = ‘四种级别’;

三、测试

1、参考 postgresql事务处理与并发控制 :https://blog.51cto.com/fengfeng688/2153042

2、官方文旦:https://www.postgresql.org/docs/9.4/static/transaction-iso.html

3、测试结果:

repeatable read

serializable

read committed

更新同一表中的同一数据 先后开启事务A、B。A更新a,B更新a。后提交的会回滚:could not serialize access due to concurrent update 先后开启事务A、B。A更新a,B更新a。后提交的会回滚:could not serialize access due to concurrent update 先后开启事务A、B。A更新a(0.109 ms),B更新b(0.113 ms)。A和B都会commit成功(但B会等A执行成功且commit之后,B才会执行成功,最后B也提交)
更新同一表中的不同数据 先后开启事务A、B。A更新a(0.197 ms),B更新b(0.202 ms)。A和B都会commit成功
先后开启事务A、B。A更新a(0.233 ms),B更新b(0.274 ms)。后提交的会回滚:

ERROR: could not serialize access due to read/write dependencies among transactions
DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt.
HINT: The transaction might succeed if retried.

先后开启事务A、B。A更新a(0.109 ms),B更新b(0.113 ms)。A和B都会commit成功

四、MVCC

1、参考资料:https://blog.csdn.net/Habren/article/details/51592969

2、insert操作 : begin—>insert—>将数据放在share_cache中,并标记为dirty(脏读问题)—>commit—>将数据持久化到磁盘

3、SELECT xmin,xmax,cmin,cmax,age(xmin),txid_current from dual;

说明:txid_courrent= 2^32 + xmin + age(xmin);

edb当前事务号= xmin+age(xmin)

edb当前事务号到达2^32时,回归

已提交的事务之间是否可见比较的是xmin的大小

通过管道链接远程服务器上面的数据库

服务器的数据库在linux上面不方便管理,又无开启数据库远程访问权限。只有ssh权限的情况下。本地通过ssh管道链接该数据库。。

首先保证服务器数据库开启,
本地链接服务器

ssh -L localhost:8080:www.baidu.com:80 root@12.54.654.255 -p 65422
ssh -L 【本地客户端IP地址】:【本地客户端端口】:【需要访问的地址】:【需要访问的服务端口】 root@115.231.100.92 -p 65422
当前客户端想访问百度地址,但是由于某些原因无法访问,但是可以访问到服务器12.54.654.255,而12.54.654.255可以访问百度,此时需要使用ssh做个管道连接。将当前客户端访问的8080端口映射为。12.54.654.255服务器上访问的百度地址。

ssh -L 20202:localhost:5432 root@12.54.654.255 -p 22
通过ssh -L 管道链接 :将地址为12.54.654.25服务器上面的 ,本地(localhost)服务端口5432(postgresql服务)映射到,客户端的本地端口20202.

至此,客户端的本地端口20202即为服务端的 5432端口服务。

20202:可配置的将服务端数据库端口映射过来的本地端口。
localhost:在服务器上面的本地服务。
5432:服务器上面的数据库服务端口。
root:服务器的ssh账号。
12.54.654.25:服务器地址。
22:服务器的ssh端口。

sequelize操纵Pgsql

Sequelize介绍


Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, SQLite 和 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 读取和复制等功能.


PostgreSQL 是一种非常先进的对象-关系型数据库管理系统(ORDBMS),目前功能最强大,特性最丰富和最先进的自由软件数据库系统。有些特性甚至连商业数据库都不具备。这个起源于伯克利(BSD)的数据库研究计划目前已经衍生成一项国际开发项目,并且有非常广泛的用户。

建立链接


Sequelize会在初始化时设置一个连接池,这样你应该为每个数据库创建一个实例:

1
2
3
4
5
6
7
8
9
10
var sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'|'mariadb'|'sqlite'|'postgres'|'mssql',

pool: {
max: 5,
min: 0,
idle: 10000
},
});

// 或者可以简单的使用一个连接 uri

1
var sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING,
field: 'first_name' // Will result in an attribute that is firstName when user facing but first_name in the database
},
lastName: {
type: Sequelize.STRING
}
}, {
freezeTableName: true // Model 对应的表名将与model名相同
});

//将表同步到数据库
User.sync({force: true}).then(function () {
// 已创建数据表
return User.create({
firstName: 'John',
lastName: 'Hancock'
});
});

Sequelize是基于Promise实现的流程控制

1
2
3
4
let user = await User.findOne();
User.findOne().then(user =>{
console.log(user);
});

数组数据的检索

1
2
3
4
5
6
//检索语句是被检索数据的子集
$contains: [1, 2]
//被检索数据是检索语句的子集
$contained: [1, 2]
//被检索数据与检索语句有交集
$overlap: [1, 2]

npm全局安装的包命令不存在

//查看全局安装包位置
npm config get prefix
修改npm命令环境前置配置
npm config set prefix “nodejs的全局安装目录”
安装需要的全局包

在机器上安装 zeromq@6.0.0-beta.6 出现权限问题需要 –unsafe-perm=true ,出现无法下载资源包问题,需要 –registry=https://registry.npmmirror.com
npm install zeromq@6.0.0-beta.6 –build-from-source –unsafe-perm=true –registry=https://registry.npmmirror.com

node安全性介绍

安全性,总是一个不可忽视的问题。许多人都承认这点,但是却很少有人真的认真地对待它。所以我们列出了这个清单,让你在将你的应用部署到生产环境来给千万用户使用之前,做一个安全检查。

以下列出的安全项,大多都具有普适性,适用于除了Node.js外的各种语言和框架。但是,其中也包含一些用Node.js写的小工具。

配置管理

安全性相关的HTTP头
以下是一些安全性相关的HTTP头,你的站点应该设置它们:

  • Strict-Transport-Security:强制使用安全连接(SSL/TLS之上的HTTPS)来连接到服务器。
  • X-Frame-Options:提供对于“点击劫持”的保护。
  • X-XSS-Protection:开启大多现代浏览器内建的对于跨站脚本攻击(XSS)的过滤功能。
  • X-Content-Type-Options: 防止浏览器使用MIME-sniffing来确定响应的类型,转而使用明确的content-type来确定。
  • Content-Security-Policy:防止受到跨站脚本攻击以及其他跨站注入攻击。

在Node.js中,这些都可以通过使用Helmet模块轻松设置完毕:

1
2
3
4
var express = require('express');
var helmet = require('helmet');
var app = express();
app.use(helmet());

Helmet在Koa中也能使用:koa-helmet。

当然,在许多的架构中,这些头会在Web服务器(Apache,nginx)的配置中设置,而不是在应用的代码中。如果是通过nginx配置,配置文件会类似于如下例子:

1
2
3
4
5
# nginx.conf
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'";

完整的例子可以参考这个nginx配置

如果你想快速确认你的网站是否都设置这些HTTP头,你可以通过这个网站在线检查:http://cyh.herokuapp.com/cyh

客户端的敏感数据

当部署前端应用时,确保不要在代码中暴露如密钥这样的敏感数据,这将可以被所有人看到。

现今并没有什么自动化检测它们的办法,但是还是有一些手段可以用来减少不小心将敏感数据暴露在客户端的概率:

  • 使用pull request更新代码
  • 建立起code review机制

身份认证

对于暴力破解的保护
暴力破解即系统地列举所有可能的结果,并逐一尝试,来找到正确答案。在web应用中,用户登陆就特别适合它发挥。

你可以通过限制用户的连接频率来防止这类的攻击。在Node.js中,你可以使用ratelimiter包。

1
2
3
4
var email = req.body.email;
var limit = new Limiter({ id: email, db: db });
limit.get(function(err, limit) {
});

当然,你可以将它封装成一个中间件以供你的应用使用。Express和Koa都已经有现成的不错的中间件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var ratelimit = require('koa-ratelimit');
var redis = require('redis');
var koa = require('koa');
var app = koa();
var emailBasedRatelimit = ratelimit({
db: redis.createClient(),
duration: 60000,
max: 10,
id: function (context) {
return context.body.email;
}
});
var ipBasedRatelimit = ratelimit({
db: redis.createClient(),
duration: 60000,
max: 10,
id: function (context) {
return context.ip;
}
});
app.post('/login', ipBasedRatelimit, emailBasedRatelimit, handleLogin);


这里我们所做的,就是限制了在一段给定时间内,用户可以尝试登陆的次数 – 这减少用户密码被暴力破解的风险。以上例子中的选项都是可以根据你的实际情景所改变的,所以不要简单的复制粘贴它们。。

如果你想要测试你的服务在这些场景下的表现,你可以使用hydra

Session管理

对于cookie的安全使用,其重要性是不言而喻的。特别是对于动态的web应用,在如HTTP这样的无状态协议的之上,它们需要使用cookie来维持状态。

Cookie标示
以下是一个每个cookie可以设置的属性的列表,以及它们的含义:

  • secure - 这个属性告诉浏览器,仅在请求是通过HTTPS传输时,才传递cookie。
  • HttpOnly - 设置这个属性将禁止javascript脚本获取到这个cookie,这可以用来帮助防止跨站脚本攻击。

Cookie域

  • domain - 这个属性用来比较请求URL中服务端的域名。如果域名匹配成功,或这是其子域名,则继续检查path属性。
  • path - 除了域名,cookie可用的URL路径也可以被指定。当域名和路径都匹配时,cookie才会随请求发送。
  • expires - 这个属性用来设置持久化的cookie,当设置了它之后,cookie在指定的时间到达之前都不会过期。

在Node.js中,你可以使用cookies包来轻松创建cookie。但是,它是较底层的。在创建应用时,你可能更像使用它的一些封装,如cookie-session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var cookieSession = require('cookie-session');
var express = require('express');
var app = express();
app.use(cookieSession({
name: 'session',
keys: [
process.env.COOKIE_KEY1,
process.env.COOKIE_KEY2
]
}));
app.use(function (req, res, next) {
var n = req.session.views || 0;
req.session.views = n++;
res.end(n + ' views');
});
app.listen(3000);

CSRF

跨站请求伪造(CSRF)是一种迫使用户在他们已登录的web应用中,执行一个并非他们原意的操作的攻击手段。这种攻击常常用于那些会改变用户的状态的请求,通常它们并不窃取数据,因为攻击者并不能看到响应的内容。

在Node.js中,你可以使用csrf模块来缓和这种攻击。它同样是非常底层的,你可能更喜欢使用如csurf这样的Express中间件。

在路由层,可以会有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var cookieParser = require('cookie-parser');
var csrf = require('csurf');
var bodyParser = require('body-parser');
var express = require('express');
// setup route middlewares
var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });
// create express app
var app = express();
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser());
app.get('/form', csrfProtection, function(req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() });
});
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});


在展示层,你需要使用CSRF token:
1
2
3
4
5
6
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>

数据合法性

以下是两种类似的,但是略有不同的攻击方式,一种关于跨站脚本,而另一种则关于存储。

  • 非持久化的XSS攻击 在攻击者向指定的URL的响应HTML中注入可执行的JavaScript代码时发生。
  • 持久化的XSS攻击 在应用存储未经过滤的用户输入时发生。用户输入的代码会在你的应用环境下执行。
    为了防御这类攻击,请确保你总是检查并过滤了用户的输入内容。

SQL注入

在用户的输入中包含部分或完整的SQL查询语句时,SQL注入就有可能发生。它可能会读取敏感数据,或是直接删除数据。

例如:

  • select title, author from books where id=$id

以上这个例子中,$id来自于用户输入。用户输入2 or 1=1也可以。这个查询可能会变成:

  • select title, author from books where id=2 or 1=1

最简单的预防方法则是使用参数化查询(parameterized queries)或预处理语句(prepared statements)。
如果你正在通过Node.js使用PostgreSQL。那么你可以使用node-postgres模块,来创建参数化查询:

1
2
var q = 'SELECT name FROM books WHERE id = $1';
client.query(q, ['3'], function(err, result) {});

命令注入

攻击者使用命令注入来在远程web服务器中运行系统命令。通过命令注入,攻击者甚至可以取得系统的密码。

实践中,如果你有一个URL:

它可以变成:

在这个例子中,%3B会变成一个分号。所以将会运行多条系统命令。
为了预防这类攻击,请确保总是检查过滤了用户的输入内容。
我们也可以以Node.js的角度来说:

1
2
3
child_process.exec('ls', function (err, data) {
console.log(data);
});

在child_process.exec的底层,它调用了/bin/sh,所以它是一个bash解释器,而不仅仅是只能执行用户程序。
当用户的输入是一个反引号或$()时,将它们传入这个方法就很危险了。
可以通过使用child_process.execFile来解决上面这个问题。

安全传输

SSL版本,算法,键长度
由于HTTP是明文传输的,所以我们需要通过一个SSL/TLS通道来加密,即HTTPS。如今高级别的加密方式已被普遍使用,但是,如果在服务端缺乏配置,也可能会导致服务端使用低级别的加密,或不加密。

你需要测试:
密码,密钥和重协商(renegotiation)都已经合法妥善得配置完毕。
证书的合法性。
使用如nmap和sslyze这样的工具可以使这项工作非常简单。

检查证书信息

  • nmap –script ssl-cert,ssl-enum-ciphers -p 443,465,993,995 www.example.com

使用sslyze来检查SSL/TSL:

  • ./sslyze.py –regular example.com:443

HSTS
在上文的配置管理章节我们已经对其有了接触 - Strict-Transport-Security头会强制使用HTTPS来连接服务器。以下是一个Twitter的例子:

  • strict-transport-security:max-age=631138519

这里的max-age定义了浏览器需要自动将所有HTTP请求转换成HTTPS的秒数。

对于它的测试是非常简单的:

拒绝服务

账号锁定
账号锁定用于缓和暴力破解带来的拒绝服务方面的影响。实践中,它意味着,当用户尝试了几次登陆并失败后,将在其后的一段内,禁止他的登陆操作。

可以使用之前提到的rate-limiter来阻止这类攻击。

正则表达式
这类攻击主要是由于一些正则表达式,在极端情况下,会变得性能及其糟糕。这些正则被称为恶魔正则(Evil Regexes):

  • 对于重复文本进行分组
  • 在重复的分组内又有重复内容([a-zA-Z]+)*, (a+)+ 或 (a|a?)+在如aaaaaaaaaaaaaaaaaaaaaaaa! 这样的输入面前,都是脆弱的。这会引起大量的计算。更多详情可以参考ReDos。

可以使用Node.js工具safe-regex这检测你的正则:

1
2
3
4
$ node safe.js '(beep|boop)*'
true
$ node safe.js '(a+){10}'
false

错误处理

错误码,堆栈信息
一些错误场景可能会导致应用泄露底层的应用架构信息,如:like: X-Powered-By:Express。

堆栈信息可能自己本身并没有什么用,但它经常能泄露一些攻击者非常感兴趣的信息。将堆栈信息返回出来是非常不好的实践。你需要将它们记录在日志中,而不是展示给用户。

NPM
更强的能力意味着更大的责任 - NPM有这许多可以现成使用的包,但是代价是:你需要检查这些包本身是否存在安全问题。

幸运的是Node Security project(nsp)是一个非常棒的工具,来检查你使用的模块是否是易被一些已知的手段攻击的。

1
2
3
4
5
6
npm i nsp -g
# either audit the shrinkwrap
nsp audit-shrinkwrap
# or the package.json
nsp audit-package


最后
这个清单主要根据OWASP维护的Web Application Security Testing Cheat Sheet所列。

Node.js 中高效的 timer

在 Node.js 中,许许多多的异步操作,都需要来一个兜底的超时,这时,就轮到 timer 登场了。由于需要使用它的地方是那么的多,而且都是基础的功能模块,所以,对于它性能的要求,自然是十分高的。总结来说,要求有:

  • 更快的添加操作。
  • 更快的移除操作。
  • 更快的超时触发。
    接下来就让我们跟着 Node.js 项目中的 lib/timer.js 和 lib/internal/linklist.js 来探究它具体的实现。

更快的添加 / 移除操作
说到添加和移除都十分高效的数据结构,第一个映入脑帘的,自然就是链表啦。是的,Node.js 就是使用了双向链表,来将 timer 的插入和移除操作的时间复杂度都降至 O(1) 。双向链表的具体实现便在 lib/internal/linklist.js 中:

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
35
36
37
38
39
40
41
// lib/internal/linklist.js
'use strict';
function init(list) {
list._idleNext = list;
list._idlePrev = list;
}
exports.init = init;
function peek(list) {
if (list._idlePrev == list) return null;
return list._idlePrev;
}
exports.peek = peek;
function shift(list) {
var first = list._idlePrev;
remove(first);
return first;
}
exports.shift = shift;
function remove(item) {
if (item._idleNext) {
item._idleNext._idlePrev = item._idlePrev;
}
if (item._idlePrev) {
item._idlePrev._idleNext = item._idleNext;
}
item._idleNext = null;
item._idlePrev = null;
}
exports.remove = remove;
function append(list, item) {
remove(item);
item._idleNext = list._idleNext;
list._idleNext._idlePrev = item;
item._idlePrev = list;
list._idleNext = item;
}
exports.append = append;
function isEmpty(list) {
return list._idleNext === list;
}
exports.isEmpty = isEmpty;

可以看到,都是些修改链表中指针的操作,都十分高效。

###更快的超时触发###

链表的缺点,自然是它的查找时间,对于一个无序的链表来说,查找时间需要 O(n) ,但是,只要基于一个大前提,那么我们的实现就并不需要使用到链表的查询,这也是更高效的超时触发的基础所在,那就是,对于同一延迟的 timers ,后添加的一定比先添加的晚触发。所以,源码的具体做法就是,对于同一延迟的所有 timers ,全部都维护在同一个双向链表中,后来的,就不断往链表末尾追加,并且这条链表实际上共享同一个定时器 。这个定时器会在当次超时触发时,动态计算下一次的触发时间点。所有的链表,都保存在一个对象 map 中。如此一来,既做到了定时器的复用优化,又对链表结构进行了扬长避短。

让我们先以 setTimeout 为例看看具体代码,首先是插入:

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
35
// lib/timer.js
// ...
const refedLists = {};
const unrefedLists = {};
exports.setTimeout = function(callback, after) {
// ...
var timer = new Timeout(after);
var length = arguments.length;
var ontimeout = callback;
// ...
timer._onTimeout = ontimeout;
active(timer);
return timer;
};
const active = exports.active = function(item) {
insert(item, false);
};
function insert(item, unrefed) {
const msecs = item._idleTimeout;
if (msecs < 0 || msecs === undefined) return;
item._idleStart = TimerWrap.now();
var list = lists[msecs];
if (!list) {
// ...
list = new TimersList(msecs, unrefed);
L.init(list);
list._timer._list = list;
if (unrefed === true) list._timer.unref();
list._timer.start(msecs, 0);
lists[msecs] = list;
list._timer[kOnTimeout] = listOnTimeout;
}
L.append(list, item);
assert(!L.isEmpty(list));
}

即检查当前在对象 map 中,是否存在该超时时间(msecs)的双向链表,若无,则新建一条。你应该已经看出,超时触发时具体的处理逻辑,就在 listOnTimeout 函数中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// lib/timer.js
// ...
function listOnTimeout() {
var list = this._list;
var msecs = list.msecs;
var now = TimerWrap.now();
var diff, timer;
while (timer = L.peek(list)) {
diff = now - timer._idleStart;
if (diff < msecs) {
this.start(msecs - diff, 0);
return;
}
L.remove(timer);
// ...
tryOnTimeout(timer, list);
// ...
}
this.close();
// ...
}

即不断从链表头取出封装好的包含了注册时间点和处理函数的对象,然后挨个执行,直到计算出的超时时间点已经超过当前时间点。

举个图例,在时间点 10,100,400 时分别注册了三个超时时间为 1000 的 timer,在时间点 300 注册了一个超时时间为 3000 的 timer,即在时间点 500 时,对象 map 的结构即为:

随后在时间点 1200 触发了超时事件,并在时间点 1300 执行完毕,彼时对象 map 的结构即为:

setInterval 和 setImmediate
setInterval 的实现总体和 setTimeout 很相似,区别在于对注册的回调函数进行了封装,在链表的尾部重新插入:

1
2
3
4
5
6
7
8
9
10
// lib/timer.js
// ...
function wrapper() {
timer._repeat(); // 执行传入的回调函数
if (!timer._repeat)
return;
// ...
timer._idleTimeout = repeat;
active(timer);
}

而 setImmediate 和 setTimeout 实现上的主要区别则在于,它会一次性将链表中注册的,都执行完:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// lib/timer.js
// ...
function processImmediate() {
var queue = immediateQueue;
var domain, immediate;
immediateQueue = {};
L.init(immediateQueue);
while (L.isEmpty(queue) === false) {
immediate = L.shift(queue);
// ...
tryOnImmediate(immediate, queue);
// ...
}
if (L.isEmpty(immediateQueue)) {
process._needImmediateCallback = false;
}
}

所以作为功能类似的 process.nextTick 和 setImmediate ,在功能层面上看,每次事件循环,它们都会将存储的回调都执行完,但 process.nextTick 中的存储的回调,会先于 setImmediate 中的执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict'
const print = (i) => () => console.log(i)
process.nextTick(print(1))
process.nextTick(print(2))
setImmediate(() => {
print(3)()
setImmediate(print(6))
process.nextTick(print(5))
})
setImmediate(print(4))
console.log('hello')
// hello
// 1
// 2
// 3
// 4
// 5
// 6

最后
参考:
https://github.com/nodejs/node/blob/master/lib/timers.js
https://github.com/nodejs/node/blob/master/lib/internal/linkedlist.js

Hexo发布到Github

首先在配置文件上填写:

1
2
3
4
5
deploy:
type: git # 设置发布类型,如git
repository: git@github.com:heyfgirl/heyfgirl.github.io.git # 设置repository对应的链接
branch: master # 设置提交到的分支
message: Site updated at {{ now("YYYY-MM-DD HH:mm:ss") }} # 设置我们提交的信息

执行发布代码命令:

1
hexo deploy

提示 error deployer not found:git(说明没有发布工具)

然后拉去发布工具

1
npm install hexo-deployer-git --save 

测试链接Github

1
ssh -T git@github.com

Permission denied(publickey)
提示缺少公钥

本地生成公钥

1
ssh-keygen -t rsa -C "xxxx@xxx.com"

成功的话会在 ~/下生成 .ssh文件夹,进去,打开 id_rsa.pub,复制里面的key即可。

在github生成密钥,将本地公钥拷贝进去
再先进行发布行为OK