0%

unix上的模式匹配glob,其名字缩写来自于global或global command

debian源安装gulp后,执行gulp -v提示:

1
/usr/bin/env: node: No such file or directory

是因为/usr/local/bin/gulp.js第一行引用的是node,而node在debian系统中实际的可执行文件为nodejs,修正之后就正常了。

restful是一种轻量级的web service实现方式。restful并不是标准,也没有对应的软件实体,只是基于http协议的一种指导性的设计方法或原则。

当下流行的open api大部分采用restful的方式实现,但restful并不限于此。普通的web app,移动app,以及系统之间的web service集成都可以采用restful的方式。

restful和http一样是无状态的,也就是单次请求之间并无任何联系,每次请求必须携带全部的状态信息。

对于受保护的资源,restful同样面临认证(authentication)和授权(authorization)的问题。

restful在不同的使用情景下有其相适宜的认证和授权方式。

第三方授权

现在火热的开放平台就是典型的三方授权模式。OAuth(Open Authorization)就是用于三方授权的协议,当前版本为2.0。
OAuth协议重在授权,应用程序无需知道资源拥有者的身份凭证,只需用户授权一定的资源访问权限,获取相应的Access Token就可以在授权范围内访问用户的资源。这种授权是有期限的,而且用户可以随时撤销。

OAuth授权整个流程涉及到用户,资源服务器,认证服务器和第三方应用这四个角色。资源服务器和认证服务器只是概念上的区分,物理上可以存在于同一个服务器。

Access Token就是与一个与用户相关的一个随机数,认证服务器记录了此Access Token所拥有的访问权限。第三方应用访问用户资源时携带Access Token,经过权限检查可以访问被授权的资源。

web应用

前后分离的web应用程序,后端可以采用restful方式向前端提供api接口。这种模式,使用传统的session方式即可满足要求。也可以采用token的方式,用户使用身份凭证通过系统身份认证后,服务端颁发一个随机的token,以后每次访问api时,参数中携带此token即可。token可以设置有效期,过期以后重新认证颁发新的token。

其实传统的session使用的sessionid就是token。无论使用cookie,url重写还是隐藏表单域,无非都是将服务器颁发的sessionid再重新发送给服务器进行认证。sessionid就是会话令牌。

session用于认证,授权则由应用程序自行处理,比如基于角色的权限系统等。

传统的session方式最大的风险在于session劫持,https可以大大缓解这一风险,可以杜绝中间人攻击。

web接口

restful方式实现web service供其他应用使用。这种情形下通过颁发app id(access key/pulic key)和app secret(secret key/private key),并对请求进行签名的方式来保证api的安全,防止有人篡改请求和非授权访问。如果签名中添加timestamp可以进一步防范重放攻击(replay attack)。

此处的access key用户标示用户的身份,客户端必须妥善保存其secret key,这是服务端认证客户端唯一可靠保证。
access key类似传统的用户名,而secret key则是两端共享的私密秘钥,以随机数生成算法生成一个较长的随机字符串即可。

客户请求api时,将请求的动作类型(get,post,put或delete)、uri、请求参数(包括access key)、timestamp使用secret key进行签名,使用HMAC-SHA256等摘要算法。将计算好的摘要同其他请求参与一同发送给服务器。服务端根据access key查找其对应的secret key,然后使用相同的算法重新计算摘要。如果重新计算的摘要与请求传送过来的摘要一致,则可以信任此次请求。添加时间戳的主要目的是用于防范重放攻击。

签名算法

下面是一个签名算法的例子。

算法描述如下:

1
2
3
4
5
signature = HMAC-SHA256(secrey_key, string_to_sign);

string_to_sign = http_verb + "&" + uri + "&" + request_parameters_sorted;

request_parameters_sorted = "key1=value1&key2=value2&..keyn=valuen";

比如以post方式访问https://foo.com/bar/test接口,请求参数为:

1
2
3
4
5
6
7
8
{
"first_name" : "kitty",
"last_name" : "san",
"age" : 8,
"gender" : "female",
"app_id" : "xdfe323423fsvdsefew",
"timestamp" : "2015-10-21 12:06:06"
}

获取当前的UTC时间戳为: 2015-10-21 12:06:06

将请求参数的key以字典序排序得到:

1
2
3
4
5
6
7
8
{
"age" : 8,
"app_id" : "xdfe323423fsvdsefew",
"first_name" : "kitty",
"gender" : "female",
"last_name" : "san",
"timestamp" : "2015-10-21 12:06:06"
}

将排序后的参数拼接得到request_parameters_sorted:

1
request_parameters_sorted = "age=8&app_id=xdfe323423fsvdsefew&first_name=kitty&gender=female&last_name=san&timestamp=2015-10-21 12:06:06";

然后得到将要签署的字符串string_to_sign

1
string_to_sign = "post&/bar/test&age=8&app_id=xdfe323423fsvdsefew&first_name=kitty&gender=female&last_name=san&timestamp=2015-10-21 12:06:06"

最后得到signature

1
signature = HMAC-SHA256('your_secret_key', "post&/bar/test&age=8&app_id=xdfe323423fsvdsefew&first_name=kitty&gender=female&last_name=san&timestamp=2015-10-21 12:06:06");

计算完签名后,将签名作为请求参数之一一起发送给服务端,参数的名称为signature。服务器端接收到请求后以相同算法重新计算签名进行核对即可。服务器端计算签名时要去掉signature参数。

所有的字符都使用UTF-8编码。直接对原始请求参数进行签名即可,无需进行url编码。服务器端接收到的也是原始请求参数,这样计算签名更简单。

防范重放攻击

因为请求中携带了请求发出时的时间戳,服务器可以设置一个合理的请求认证时间窗口,比如10分钟,在当前时间前后10分钟之内的请求都可以视为合法请求。这只是减少了被重放攻击的可能性,但并未完全杜绝重放攻击。如果请求在服务器时间窗内被截获重放,则只靠时间戳是无能为力的。

因此,需要附加另外的机制来防止重放攻击。服务端可以记录每次请求的时间戳和签名,每次请求到达是,先验证请求是否在时间窗口范围内,如果超出时间范围则直接拒绝。如果在时间窗口内,则查询请求记录,如果没有对应的请求记录,则满足此次请求,并将此次请求的时间戳和签名记录下来,并清理掉不在当前时间窗口内的所有请求。

时间戳加记录请求的方式可以完全杜绝重放攻击,而且可以保持一个很小的请求记录表。因为在当前时间窗口外的请求可以随时被清理掉。

请求速率限制

如有需要可以对api请求的速率或次数进行限制。

非对称秘钥加密签名

也可以使用RSA非对称秘钥进行数字签名。

服务端生成RSA公私密钥对和客户端的access key,将私钥和access key交付客户端应用。服务端保存access key和客户公钥。

请求签名时,客户端不再使用HMAC签名算法,而是使用普通的摘要算法,比如SHA256,但此时传送的摘要使用客户私钥进行加密后再随请求参数一起传递。因为谁都可以计算SHA256摘要,所以需要用私钥进行加密保护。

服务端接收到请求后,使用同样的算法计算SHA256摘要,然后使用客户的公钥解密随请求一起发的、客户端计算的、加密后的摘要,如果服务端重新计算的摘要与解密后的摘要相同,则认为请求是合法。

无论使用对称秘钥还是非对称秘钥进行签名,秘钥都要妥善保存,这是整个签名认证算法的基石。

无论何种情形,对于restful api的安全而言,https/ssl加密都是十分有必要的。

注意:使用access key与secret key并对api签名调用的方式,并不适合web前端或者移动app使用。因为web前端无法保密secret key,而将secret key保存在android或ios app中也是无法保证安全的,很容易将secret key从app中破解出来。只有将secret key保存在后端才能保证安全。并且secret key是针对客户端发放的,而不应该是针对每一个客户端的user发放的。

===
[erq]

IE版本检测有很多方法,特性检测是比较好的一种方式。

非标准的document.all对象只存在于IE10及以下版本。其实,其他浏览器比如chrome也实现了document.all对象,不过在这些浏览器中以布尔方式判断document.all对象都是返回undefined的。

可以这样测试:
[javascript]

[/javascript]

再佐以其他IE版本相关的特性,详见参考[1],可以有如下的IE版本检测代码:
[javascript]
var v;

if (document.all) {
if (window.atob) {
v = ‘10’;
}
else if (document.addEventListener) {
v = ‘9’;
}
else if (document.querySelector) {
v = ‘8’;
}
else if (window.XMLHttpRequest) {
v = ‘7’;
}
else if (document.compatMode) {
v = ‘6’;
}
else {
v = ‘5.5 or older’;
}

v = ‘IE’ + v;
}
else {
v = ‘IE11+ or not IE’;
}

console.log(‘Your browser is’ + v);

[/javascript]

References:
[1]Internet Explorer (IE) version detection in JavaScript
[2]JavaScript判断IE各版本最完美解决方案

===
[erq]

非标准、非通用的二进制插件打印方式此处不叙。

打印命令

  • 标准的打印方法为调用window.print(),所有的现代浏览器都支持该方法。

  • 还可以使用Print命令调用Document.execCommand(),也就是:

    1
    document.execCommand('Print')

    虽然所有的桌面浏览器都支持,但这个方法是非标准的。

    execCommand的接口规格:

    1
    bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)

局部打印方法

Javascript局部打印大约有一下这几种方法:

  • print css方式

html支持print css,这是专用于打印设备的,而通常的css用于显示设备

1
<link rel="stylesheet" href="print.css" media="print" />

可以使用print css将无需打印的区域隐藏掉,而需要打印的区域重新设置适合打印的样式,打印时直接调用window.print()即可。

这种打印方式页面不会看到变化,但是样式的调整可能会比较繁琐。

  • screen css方式

使用通常的CSS,在打印之前将无需打印的部分从页面上隐藏,需要打印的区域冲洗设置适合打印的样式。打印完成后再恢复样式。打印时会看到页面的变化。

  • body replace方式

打印之前将页面的body内容替换为需要打印的区域,打印完毕后再恢复body的内容。打印时会看到页面的变化。
这种方式与上种方法虽然做法不同,但其实质是相同的,即讲当前页面显示的内容设置要打印的内容,打印完毕后再恢复页面。

  • iframe方式

打印时生成一个iframe嵌入到主页面中,iframe的内容即为需要打印的内容,然后调用iframe的print()方法就可以了。iframe其实就是一个window。
用iframe的好处是不用弹出新窗口。

但是需要注意:
chrome从45.0开始,默认阻止iframe的print方法,除非iframe的沙箱属性有allow-modal值,并且设置了modal标志。

Starting with Chrome 45.0 print method is blocked inside an iframe unless its sandbox attribute has the value allow-modal and the modal flag is enabled.

  • popup new window方式

new window方式其实与iframe方式基本一样,唯一的区别是需要弹出新窗口。很多浏览器对于弹出新窗口都有严格的管制策略,因此新窗口方式用户体验不佳。

总的来说,iframe的方式比较简单,用户体验也较佳。

===
[erq]

嗯,这篇是转帖的,因为GFW blocked this post,又没有再写一篇的必要。

Homebrew 是一套在 Mac OS X 下使用的套件管理工具,以往大家會使用 Mac Ports ,但是 port 的套件相依性太深,常常會為了裝個小套件而跟著裝上一堆用不到的相依套件,甚至造成套件版本衝突…… 因此 Homwbrew 甫一推出立刻受到大家的歡迎。

今天早上我剛好重裝 readline 套件,安裝完成後訊息提示我 readline 套件是 「keg-only」。我為了查出「keg-only」到底是指什麼意思,結果解開一連串的謎題,真相終於大白! Homebrew 的所有命名真的非常有邏輯~~

首先, brew 本身是釀造、釀酒的意思,會用這個字的原因是 homebrew 的安裝方式為下載 source code 回來做編譯,由於是在自己電腦做 local complie 編譯套件,所以這個工具叫做 homebrew 自家釀酒。

釀酒需要有配方 formula ,當你需要安裝套件時,流程就是下 brew 命令去根據配方 formula ,釀造出一桶(keg)酒來。所以 keg 指的是整個編譯完成的套件資料夾。

再來,放置套件的位置在 /usr/local/Cellar , Cellar 就是地窖,一桶一桶釀好的酒當然要存放在地窖裡囉!所以編譯完成的套件資料夾 keg 預設目錄在 /usr/local/Cellar 。

最後回到「keg-only」這個詞,字面上意思現在就很清楚,表示這個套件只會存放在桶子裡,不會跑出桶子外;實際上的行為是 brew 不會幫你做 symlink 到 /usr/local ,避免你的原生系統內還有一套 readline 而打架,所以訊息提示說 readline 套件是 keg-only 。

至此謎題全部解開啦! Homebrew 的命名邏輯真是超有趣的~

===
[erq]

传统的eth0,eth1,wlan0,wlan1等网络接口名字,在多网络接口卡的环境下,有多种因素会导致网络接口的名字与实际对应的网络设备发生变化,比如这次的eth0,可能下次系统重新启动后会变成eth1,这自然会导致很多很多问题。

当然出现了很多解决方案来避免这个问题,比如udev可以使一个网络接口的名字与网络接口的MAC地址绑定,从而只要还是那块网卡,对应的网络接口的名字就还是那个名字。

还有一种解决方案为”biosdevname”,当然systemd认为这些方案都有各种各样的问题,于是又有了它自己的解决方案。

systemd果然像有些人所言,想要控制一切!

systemd自v197开始使用自己的命名方案,但是只有全新安装的系统才默认使用这个命名规则,系统升级而来继续使用原先的方案。

所以这次系统重装后,发现wlan接口名字变成了

1
2
3
4
5
6
7
8
$ iw dev
phy#0
Interface wlx2016d803b954
ifindex 2
wdev 0x1
addr 20:16:d8:03:b9:54
ssid xxxxxxxx
type managed

竟然用的是MAC地址,如果MAC地址改变了呢?

查看内核信息:

1
2
$ dmesg grep wlan0
\[ \] rtl8723au 1-1.4:1.2 wlx2016d803b954: rename from wlan0

原来是systemd给改了名字,各种不适应,名字各种长。

可以禁用这种命名方式:

1
# ln -s /dev/null /etc/udev/rules.d/80-net-name-slot.rules

或者给内核传递启动参数:

1
net.ifnames=0

随便吧。

References:
[1]Predictable Network Interface Names
[2]network interface naming with systemd 197

===
[erq]

  • 查看设备名

    1
    2
    3
    # iwconfig
    # iw dev
    # ip link
  • 启动接口

    1
    2
    # ifconfig wlan0 up
    # ip link set wlan0 up
  • 关闭接口

    1
    2
    # ifconfig wlan0 down
    # ip link set wlan0 down
  • 扫描热点

    1
    2
    # iwlist wlan0 scan
    # iw dev wlan0 scan
  • 连接到WPA/WPA2加密热点

    1
    2
    # wpa_supplicant -i interface -c <(wpa_passphrase your_SSID your_key) -B
    -B参数使wpa_supplicant背景运行
  • 动态获取IP地址

    1
    # dhclient wlan0
  • 设置静态ip地址

    1
    2
    # ip addr add 192.168.0.2/24 dev wlan0
    # ip route add default via 192.168.0.1
  • 查看接口状态

    1
    2
    3
    # iw dev wlan0 link
    # iwconfig wlan0
    # ifconfig wlan0

References:

[1]: Wireless network configuration

===
[erq]