snap/snapcraft 使用指南

为每个 Linux 桌面、服务器、云端或设备打包任何应用程序,并且直接交付更新。

snapcraft 是一个正在为其在 Linux 中的地位而奋斗的包管理系统,它为你重新设想了分发软件的方式。这套新的跨发行版的工具可以用来帮助你构建和发布 snap 软件包。接下来我们将会讲述怎么使用 CircleCI 2.0 来加速这个过程以及一些在这个过程中的可能遇到的问题。

# 介绍

> Channels

Channels allow users to subscribe to different release cadences for an app, be it by major version bumps or in-development releases.

$ sudo snap install docker –channel=17.03/stable

# 常用使用说明

1
2
3
4
5
6
7
8
9
$ snap download hello-world

# Fetching snap "hello-world”
# Fetching assertions for "hello-world”

# $ sudo snap ack hello-world_27.assert
# $ sudo snap install hello-world_27.snap

hello-world 6.3 from 'canonical' installed
1
2
3
4
5
$ snap list

# Name Version Rev Developer Notes
# <snip>
# hello-world 6.3 27 canonical -

# 背后的原理

现在系统装软件,都是用这个装的了。

/snap 目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:/snap$ tree -L 1
.
├── bin
├── chromium
├── core
├── core18
├── gnome-3-26-1604
├── gnome-calculator
├── gnome-characters
├── gnome-logs
├── gnome-system-monitor
├── gtk-common-themes
├── node
├── postman
└── README

# 常用软件安装

Package any app for every Linux desktop, server, cloud or device, and deliver updates directly.

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
find
abort Abort a pending change
ack Adds an assertion to the system
alias Sets up a manual alias
aliases Lists aliases in the system
buy Buys a snap
changes List system changes
connect Connects a plug to a slot
disable Disables a snap in the system
disconnect Disconnects a plug from a slot
download Downloads the given snap
enable Enables a snap in the system
find Finds packages to install
get Prints configuration options
help Help
info show detailed information about a snap
install Installs a snap to the system
interfaces Lists interfaces in the system
known Shows known assertions of the provided type
list List installed snaps
login Authenticates on snapd and the store
logout Log out of the store
prefer Prefer aliases from a snap and disable conflicts
refresh Refreshes a snap in the system
remove Removes a snap from the system
revert Reverts the given snap to the previous state
run Run the given snap command
set Changes configuration options
tasks List a change's tasks
try Tests a snap in the system
unalias Unalias a manual alias or an entire snap
version Shows version details
watch Watch a change in progress
  • Download snap “core” (3017) from channel “stable” (The download has been cancelled: context canceled)

core 16-2.28.1 canonical - snapd runtime environment

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
$ sudo snap install --help
Usage:
snap [OPTIONS] install [install-OPTIONS] <snap>...

The install command installs the named snap in the system.

Application Options:
--version Print the version and exit

Help Options:
-h, --help Show this help message

[install command options]
--channel= Use this channel instead of stable
--edge Install from the edge channel
--beta Install from the beta channel
--candidate Install from the candidate channel
--stable Install from the stable channel
--devmode Put snap in development mode and disable security confinement
--jailmode Put snap in enforced confinement mode
--classic Put snap in classic mode and disable security confinement
--revision= Install the given revision of a snap, to which you must have developer access
--dangerous Install the given snap file even if there are no pre-acknowledged signatures for it, meaning it was not
verified and could be dangerous (--devmode implies this)
--unaliased Install the given snap without enabling its automatic aliases

一个问题,这玩意太慢了,有没有办法加个代理,换个 mirror
或者先离线下载,然后再手动安装呢????

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Usage:
snap [OPTIONS] <command>

Install, configure, refresh and remove snap packages. Snaps are
'universal' packages that work across many different Linux systems,
enabling secure distribution of the latest apps and utilities for
cloud, servers, desktops and the internet of things.

This is the CLI for snapd, a background service that takes care of
snaps on the system. Start with 'snap list' to see installed snaps.


Application Options:
--version Print the version and exit

Help Options:
-h, --help Show this help message

Available commands:
abort Abort a pending change
ack Adds an assertion to the system
alias Sets up a manual alias
aliases Lists aliases in the system
buy Buys a snap
changes List system changes
connect Connects a plug to a slot
disable Disables a snap in the system
disconnect Disconnects a plug from a slot
download Downloads the given snap
enable Enables a snap in the system
find Finds packages to install (aliases: search)
get Prints configuration options
help Help
info show detailed information about a snap
install Installs a snap to the system
interface Lists snap interfaces
interfaces Lists interfaces in the system
known Shows known assertions of the provided type
list List installed snaps
login Authenticates on snapd and the store
logout Log out of the store
logs Retrieve logs of services
prefer Prefer aliases from a snap and disable conflicts
refresh Refreshes a snap in the system
remove Removes a snap from the system
restart Restart services
revert Reverts the given snap to the previous state
run Run the given snap command
services Query the status of services
set Changes configuration options
start Start services
stop Stop services
switch Switches snap to a different channel
tasks List a change's tasks (aliases: change)
try Tests a snap in the system
unalias Unalias a manual alias or an entire snap
version Shows version details
watch Watch a change in progress
whoami Prints the email the user is logged in with.

# 实例

安装 Node 10 LTS

https://snapcraft.io/node

sudo snap install node –channel=10/stable —classic

竟然自带了 yarn

删除呢?

Linux 环境变量配置(翻新)

Linux 是多用户操作系统,每个用户登陆后都有属于自己的环境,定义着自己专有的变量值。同时 Linux 是基于文件,和 Windows 的注册表很不相同。

本文是基于 Ubuntu 所写,其它 Linux 系统设置其本雷同。2018/12/18 升级内容到 18.04 版本。

举例来说,在终端下,你输入一个命令,会出现 Command not found , 但系统中已经安装过该程序,只是不在标准位置,此时你只能通过全路径的方式来执行 /usr/local/app/bin/run,但每次输入很是繁琐,这时就可以通过添加该目录到系统环境变量 PATH 中来实现执行文件的查找。

http://imcn.me/wp-content/uploads/2010/04/ubuntu-ad1.jpg


# 变量认识及使用

> 显示当前所有的变量值

环境变量是在终端(Shell)中被使用,也可以通过 Shell 来对其进行操作,所有变量值均使用大写。

打印所有变量值:

1
2
$ env
$ export

打印系统 PATH 中的所有路径:

1
$ echo $PATH

打印 USER 变量值:

1
$ echo $USER

也可通过 set 来显示本地定义的变量集

> 设置新的变量

在 Shell 中,可以通过 export 命令来设置新的变量,如下,创建新的变量 MYVAR

1
2
$ export MYVAR=”lanvige.com”
$ echo $MYVAR

> 使用 readonly 命令创建只读变量

1
2
$ export MYVAR=”lanvige.com”
$ readonly MYVAR

> 清除环境变量

在 Shell 中可以使用 unset 命令来清除变量值

1
$ unset MYVAR

> 常用到的环境变量

1
2
3
4
5
PATH          决定了shell将到哪些目录中寻找命令或程序
HOME       当前用户主目录
LOGNAME/USER   当前用户的登录名
HOSTNAME     指主机的名称
SHELL       前用户Shell类型


# 环境变量模式

> interactive & non-interactive | 交互式和非交互式

其实 BASH 有两种工作模式,就是:交互式和非交互式

交互式模式:就是在终端上执行,shell 等待你的输入,并且立即执行你提交的命令。这种模式被称作交互式是因为shell与用户进行交互。当你退出后,shell也终止了。

非交互模式:以 Shell Script(非交互) 方式执行。在这种模式下,Shell 不与你进行交互,而是读取存放在文件中的命令,执行它们。当它读到文件的结尾EOF,Shell 也就终止了。

区别:读取的配置文件不同,交互式读取 ~/.bashrc 文件;非交互式读取的是环境变量 BASH_ENV(通常情况下)所指定的配置文件。

> login shell & non-login shells | 登陆 Shell 与非登陆 Shell

用文本方式登录之后,得到的就是一个 login shell,如果在这个 shell 里执行一下 bash 命令,就得到一个 non-login shell

对于用户来说,login shellnon-login shell 的主要区别是:启动 shell 时所执行的 startup 文件不同。

  • 登陆 shell 执行文件为:/etc/profile~/.profile~/.bashrc
  • 非登陆 shell 执行的文件仅为:~/.bashrc,如果在运行之前,修改 profile 文件的话对它不起作用。。

~/.profile: 是 interactive & login shell 方式进入 bash 运行的
~/.bashrcinteractive & non-login 方式进入 bash 运行的

通常二者设置大致相同。在系统中前者会调用后者。


# 配置环境变量文件

> 认识环境变量文件

在 Ubuntu 18.04 中有如下文件可以设置环境变量,对应着系统级别[所有用户]和用户级别[个人用户],分别放在 /etc/~/ 下。同时这几个文件有着明确执行顺序。

- /etc/profile || /etc/bash.bashrc

系统登录时,定制用户环境时使用的第一个文件,为系统的所有用户设置环境变量,当用户第一次登录时,该文件被执行。

profile 中调用了 bash.bashrc。

- ~/.profile

交互式登录 shell - interactive & login shells,在 login 方式登陆时,该文件被执行,且仅执行一次。

该文件会在每次bash shell打开时被执行,在当前Shell中添加环境变量。

- ~/.bashrc

交互式非登录 shell - interactive non-login shells

当登录时以及每次打开新的 shell 时,该文件被读取。(non-login 形式执行时,读取此文件。若是以 login 形式执行,则不会读取此文件)
某用户专有设定文档,可以设定路径,命令别名,每次 shell script 的执行都会使用它一次

- ~/.bash_logout

当每次退出系统(退出bash shell)时,执行该文件。

> 各种模式的读取顺序

  • 图形模式登录:/etc/profile > ~/.profile
  • 图形模式登录后,打开终端时:/etc/bash > bashrc > ~/.bashrc
  • 文本模式登录时,顺序读取:/etc/bash > /etc/bashrc > /etc/profile > ~/.bash_profile
  • 从其它用户 su 到该用户,则分两种情况:
    • 如果带-l参数(或-参数,–login参数),如:su -l username,则bash是lonin的,它将顺序读取以下配置文件:/etc/bash > bashrc > /etc/profile > ~/.bash_profile
    • 如果没有带 -l 参数,则 bash 是 non-login 的,它将顺序读取:/etc/bash > /etc/bashrc > ~/.bashrc
  • 注销时,或退出su登录的用户,如果是longin方式,那么bash会读取:~/.bash_logout
  • 执行自定义的shell文件时,若使用bash -l a.sh的方式,则bash会读取行:/etc/profile > ~/.bash_profile,若使用其它方式,如:bash a.sh./a.shsh a.sh(这个不属于bash shell),则不会读取上面的任何文件。


# 实例:设置永久环境变量

刚已经演示如何添加环境变量,但通过 export 方式所添加变量值的生命周期仅为当前 shell,重新打开新的 shell 时,该变量会丢失,如何设置一个永久的变量值,可以根据你的需求将变量值添加到上述5个文件中的其中一个来实现。

如果我们想在变量中加入新的path,则可以如下:

1
$ vim ./bashrc

添加如下行在未尾,这里需考虑 PATH 查找优先级,如一个命令在不同的 PATH 中出现,会使用最前定义的 PATH 值,多个 PATH 间用:隔开。

1
export PATH="PATH=$PATH:<PATH 1>:<PATH 2>"

使其生效

刚有提到,当改变配置文件时,重启shell才能使其生效,但通过source命令也能达到同样的效果,如果修改的是.bashrc文件,则输入。

1
$ source ~/.bashrc


FAQ::


> rc 全称

配置文件 .bashrcrc 的含义是 resource configuration

> ISSUE: 执行脚本时的问题:

在 Unicorn&Nginx 的 Init 脚本配置中,需要使用 rbenv 中的shims,但 rbenv 的环境变量被配置在了~/.bashrc中,如果直接执行像unicorn来启动服务是没有问题的,但在脚本文件中调用 unicorn 就不行。会有 Command Not Found 的问题,在 sh 文件中配置 PATH 也不会生效。最后将rbenv的环境变量配置移动到 ~/.profile 中问题就解决了。


# REF::


Babel v7 指南

  1. 1. 概念入门
    1. 1.1. > plugin
    2. 1.2. > preset
    3. 1.3. >> preset-env
    4. 1.4. > 同时使用
  2. 2. 配置
    1. 2.1. > 配置文件 .babelrc
    2. 2.2. > JS code
    3. 2.3. > CLI Parameter
  3. 3. 开始使用
    1. 3.1. > @babel/cli | 静态编译
    2. 3.2. > @babel/register | 动态编译
    3. 3.3. > @babel/node | 动态编译
  4. 4. 实战
  5. 5. REF::

Babel 7 做出了重大升级,之前的配置已经完全不能使用。在项目升级到 Node 10 后,旧版本的 Babel 会报很多的错误,借机做了一次升级。


概念入门

Babel 是一个转码器,它可以将 JavaScript 中的一个语法进行编译转换,以让旧的引擎支持。

这里有两个重要的概念,plugin 和 preset。

> plugin

Babel 本质就是一个编译器,有着常见的三步:解析,转义和输出。哪些代码需要被转义,就是在 plugin 中定义的。plugin 对应的是 feature。

可以在这里看到各个版本中对应的更新和 node 的支持度:http://node.green/

下面列出了 ECMA 2015 版本中的变化点。每个点都是一个 plugin。

  • arrow-functions
  • block-scoped-functions
  • block-scoping
  • classes
  • computed-properties
  • destructuring
  • duplicate-keys
  • for-of
  • function-name
  • instanceof
  • literals
  • new-target
  • object-super
  • parameters
  • shorthand-properties
  • spread
  • sticky-regex
  • template-literals
  • typeof-symbol
  • unicode-regex

更多版本 plugin 参见:https://babeljs.io/docs/en/plugins

> preset

preset 如果单独配置就会很长,很繁琐。而一些定义好的组合,被称为 preset,可以让更多的重用。

在 Babel 7 之前,前用的 preset 是 latest, es2015, es2016, es2017。之后版本中,去掉了这种方式,换成了 stage-x 和 env。

目前常用的 Preset 为:

  • env
  • stage-0
  • stage-1
  • stage-2
  • stage-3
  • flow
  • react
  • minify
  • typescript

>> preset-env

env 代替了 latest,官方主要推荐使用这种,env 就是根据你要使用的场景,智能帮你加载所需要的 plugins。

它的默认配置如下:(但这样是很不推荐的,它会把所有的 plugins 都加载上,也就是会翻译为最古老的代码,增加翻译时间。)

1
2
3
{
"presets": ["@babel/preset-env"]
}

推荐配合使用 targets,常用如下:

  • targets.esmodules bool 是否使用 esmodules
  • targets.node 配置 nodejs 的版本号
  • targets.browsers 配置浏览器的版本,智能加载所需要的 plugins
  • include / exclude 强制使用/不使用某个 plugins

> 同时使用

如果同时配置了 preset 和 plugin 时,编译器会按如下顺序进行解析:

  • Plugins 优先
  • Plugins 列表内的优先级是自前到后
  • Preset 里的优先级则从后到前


配置

Babel 是通过配置中的 Plugins 来进行编译,那如何使用配置呢?

Babel 有几种使用方式

  • Configuration File
  • JS Code
  • CLI Parameter

> 配置文件 .babelrc

1
2
3
4
5
6
{
"presets": [[
"@babel/preset-env", { "targets": { "node": "10.4.1" }}]
],
"plugins": ["@babel/plugin-transform-modules-commonjs"]
}

> JS code

这种是在动态注入时使用,一般会应用于开发环境。用于 @babel/register 模式中。

1
2
3
4
5
require("@babel/register")({
presets: [[
"@babel/preset-env", { targets: { node: "10.4.1" }}]
]
});

> CLI Parameter

这里的 CLI 指的是 @babel/cli 和 @babel/node。

但参数的更高阶用法没有找到,比如说在 presets 后面使用 target

1
$ babel script.js --out-file script-compiled.js --presets=@babel/preset-env


开始使用

在使用场景上,会有两种方式:

  • 静态编译
  • 动态编译

> @babel/cli | 静态编译

通过下面命令来编译 js,然后将编译好的文件进行线上环境发布。

1
2
3
4
5
$ babel script.js
# 额外指定配置
$ babel script.js --out-file script-compiled.js --presets=es2015,react
# 忽略 babelrc
$ babel --no-babelrc script.js --out-file script-compiled.js --presets=es2015,react

> @babel/register | 动态编译

@babel/register 模块改写了 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js、.jsx、.es和 .es6 后缀名的文件,就会动态先用 Babel 进行转码,再进行 require。

另外,由于它是实时转码,所以会产生效率问题,只适合在开发环境使用。

需要注意的是,babel-register 只会对 require 命令加载的文件转码,而不会对当前文件转码。

1
2
3
4
5
6
7
require("@babel/register")({
presets: [[
"@babel/preset-env", { targets: { node: "10.4.1" }}]
]
});

require("./src/index.js");

> @babel/node | 动态编译

Babel 提供了 @babel@node,作为 @babel@register 的更高阶的封装,可以 runtime 编译,而且不用额外的中间层代码。

1
$ babel-node --debug --presets es2015 -- script.js --debug


实战

Server 端用 Node.js 10.4.1,开发中用到的 feature 列表中,只有一项还暂不支持:

  • es2015 module-commonjs

所以在配置 babel 时,根据自己的需求,只需要单独引入就可以了,这样,编译出来的代码几乎相同了,.babelrc 配置如下:

1
2
3
4
5
{
"presets": [[
"@babel/preset-env", { "targets": { "node": "10.4.1" }}]
]
}

也可以写成:

1
2
3
{
"plugins": ["@babel/plugin-transform-modules-commonjs"]
}


REF::

IT 设备折腾记 - 显示器的坏点

iPhone X

东京银座的 Apple Store,银山银海,找一个欧美相工作人员,终于可以愉快的交流了,😂。

然后开始进入购买排队流程,那个小队都快排出店外了。本以为可以用公司的 Global EPP,结果查不到,好在免税。最终价格是不到 8K,为什么一购物日元就上涨呢?

Face ID 真不错,不再有手汗的困扰了,但应用场景也不像 Touch ID 那样广了,各有利弊。

不过自从有了一张电信的大大流量卡之后,就很渴望 iPhone 能出个双卡双卡,Apple Watch 3 的 eSIM 让人看到了希望。

DELL 显示器有了坏点

意外发现在左上角有一个坏点,不知什么时间的事。

这种问题,属于心理影响大于实际影响很多倍的典型案例。

不仔细看,几乎不会发现,但自从有了这个点,没事就会找一下,然后感觉就很不爽,继而更不爽。

不得不说,DELL 的服务还是很赞的,直接换了新机,不然真不知道如何处理。卖掉有点会掉价,不卖自己也不爽。

以后只买 DELL UltraSharp,恩!

其实生活中也是,好多事,有了心结,难过会大大超越事情本身。

Filco 87

是的,那块 87 键的垫板出价太低了,以致于舍不得,于是花了大一千块配了个 87 的键盘。

当然了,理由很多,双模,可用 fn,调节音量,屏幕。更好用的方向键。

多说无益,入就入了,这个品牌不会让人感到不值。

DJI OSMO+

说到不值,感受最深的就是 OSMO+,这个东西的使用场景太受限了,而且质量也不怎么好。

现在是放那贬值,想出出不掉。。。

以至于让我这个数码爱好者,都不再敢轻易对未测试新物下单。

显示器 - 4K? 5K

键盘不再纠结了,总要有个事惦记吧,于是显示器又上心头。

5K 太少了,价格高到不行,那就看看 4K 吧。但又总觉得 4K 用哪个分辨率都不行,开 HiDPI 不行,不开也不行。。。

也不是没有完美参数的,像 UP3218K,除了价格 :(

通过上面的参数来看,PPI 上 200,且价格能接受的,就只有 LG 5K 了,本想去 HK 背个回来的,那边的价格才 7xxx,心动。去 Apple Store 看了下,感觉好差,整个屏很不清晰,应该是垃圾桶带不动 5K 的缘故吧,做工也不是很精致,最主要的是丑,丑的对不起价格。边上的 iMac 5K 就超清晰,可惜不支持 TDM。

或者 U2718Q 吧,除了 4K,不支持 Type-C,其它都好 -^_^-

iPhone - 217 电池

终于约到了 Apple 的电池更换服务,周未去换了下,整个机器的性能、待机时间有着明显的提升。感觉 6s Plus 又可以再战两年。

工作人员把 iPhone 连上 WiFi,然后就可以用 iPad 来操作,进行检测,最后生成一个检测报告,那叫一个专业。

不要经常在 20% 以下电量使用,对电池损耗会小一些。

JavaScript - 循环和迭代

用了这么久的 JS,经常被它的循环方法绕晕,这几种方法的区别,优劣,在此做一个详细总结。

  • for
  • while
  • forEach
  • for…in
  • for…of
  • map


🥈 for / 循环代码块一定的次数

一个for循环会一直重复执行,直到指定的循环条件为fasle。 JavaScript的for循环和Java与C的for循环是很相似的。一个for语句是这个样子的:

1
2
for ([initialExpression]; [condition]; [incrementExpression])
statement

使用示例

1
2
3
for (var i = 0; i < options.length; i++) {
statement
}

break

终止一个 for 循环

continue

跳过余下代码逻辑,直接进入下一个循环。


🥈 While / do…while

一个 while 语句只要指定的条件求值为真(true)就会一直执行它的语句块。一个 while 语句看起来像这样:

1
2
while (condition)
statement

break

终止一个 while 循环

continue

跳过余下代码逻辑,直接进入下一个循环。


🥈 forEach / 数组遍历

forEach 是 ES5 中加入的新的函数,对数组的每个元素执行一次。

1
2
3
array.forEach(callback(currentValue, index, array){
//do something
}, this)

优点是语法简洁;

缺点是不支持使用 break 来中断,也不能 return 进行返回。

配合箭头函数

1
array.forEach(n => console.log(n));

async

forEach 是不支持 async function 的。Polyfill

但可以创建自己的 asyncForEach 方法来方便使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// define
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}

// using
const waitFor = (ms) => new Promise(r => setTimeout(r, ms))

const start = async () => {
await asyncForEach([1, 2, 3], async (num) => {
await waitFor(50)
console.log(num)
})
console.log('Done')
}

start()


🥈 for…in / 遍历对象的属性

这个 for…in 语句循环一个指定的变量来循环一个对象所有可枚举的属性。JavaScript 会为每一个不同的属性执行指定的语句。

1
2
3
for (variable in object) {
statements
}

for-in 是为普通对象设计的,可以遍历得到字符串类型的键,因此不适用于数组遍历。

  • 数组的 for-in 循环体除了遍历数组元素外,还会遍历自定义属性
  • 一些情况下,for…in 代码可能按照随机顺序遍历数组元素


🥈 for…of / 全新迭代器循环

for...of 是 ECMA2015 中新加入的,用于可迭代对象上创建循环。

像常见的 Array, Map, Set,String, Collection, 有enumerable属性的对象, generators 等。

缺点

没有 index,要自己实现。

语法

1
2
3
for (variable of object) {
statement
}

示例(与 for…in 的区别)

下面的这个例子展示了 for…of 和 for…in 两种循环语句之间的区别。与 for…in 循环遍历的结果是数组元素的下标不同的是, for…of 遍历的结果是元素的值:

1
2
3
4
5
6
7
8
9
10
let arr = [3, 5, 7];
arr.foo = "hello";

for (let i in arr) {
console.log(i); // logs "0", "1", "2", "foo"
}

for (let i of arr) {
console.log(i); // logs "3", "5", "7" // 注意这里没有 hello
}

async

for…of 是支持异步使用的,这也是我们常用来进行 async 等待的方法。

1
2
3
4
5
let page = 0;
for (let i of array) {
const products = await fetchProducts(conn, page)
page++;
}


🥈 map

map reduce 是新的高阶函数。它遍历数组中的每一个元素,对期进行一些操作,然后返回一个新的数组。

配合箭头函数

1
array.map(n => console.log(n));


REF::


HTTP Authentication

今天我们来学习一个古老的话题,认证,这可是随着网络出现就有的一个东西,它的主要功能就是知道你是谁。

HTTP 提供了一套标准的身份验证框架:服务器可以用来针对客户端的请求发送质询 (challenge),客户端根据质询提供身份验证凭证。质询与应答的工作流程如下:服务器端向客户端返回 401(Unauthorized,未授权)状态码,并在 WWW-Authenticate 头中添加如何进行验证的信息,其中至少包含有一种质询方式。然后客户端可以在请求中添加Authorization 头进行验证,其 Value 为身份验证的凭证信息。

HTTP 协议提供了一种通用的框架来处理认证和访问控制,像从最早时的 HTTP 1.0 协议 RFC 1945Section 11.1 中提出的 Basic Auth, 到后面更新的 HTTP/1.1: Authentication

MDN 上有一个介绍的专题

网络变的越来越复杂,认证的方式也在不断的升级,在当下,由 OAuth 所提出的 Bearer Authorization 成为了主流。


⭐️ ️️理解 Authentication

由于 HTTP 是无状态的,对于每一个请求,你是谁就是一个问题,所以需要在每一个请求体上附带一个信息,这个信息的作用就是我是谁。

HTTP Authentication 协议约定在 Header 中加入一个条目,然后约定 Key 和 Value 的格式。

我们把 Key 称为 Request Header Field,而 Value 中的格式又是千变万化的,为此,又有不同的标准出现,于是就有了 Authorization Scheme 来进行区分,而 Value 的格式则为

1
scheme auth-token

其中 Scheme 常见的有 Basic, Bearer, OAuth 及其它标准。


Request Header Field:

  • Authenticate
  • WWW-Authenticate
  • Proxy-Authorization
  • Proxy-Authenticate

什么区别:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization

Authorization:


⭐️ ️️HTTP Authorization Scheme

HTTP Authentication Scheme Registry

在HTTP标准验证方案中,我们比较熟悉的是 “Basic” 和 “Digest”,前者将用户名密码使用BASE64编码后作为验证凭证,后者是Basic的升级版,更加安全,因为Basic是明文传输密码信息,而Digest是加密后传输。在前文介绍的Cookie认证属于Form认证,并不属于HTTP标准验证。

The general HTTP authentication framework is used by several authentication schemes. Schemes can differ in security strength and in their availability in client or server software.

The most common authentication scheme is the “Basic” authentication scheme which is introduced in more details below. IANA maintains a list of authentication schemes, but there are other schemes offered by host services, such as Amazon AWS. Common authentication schemes include:

🔅 几种常见的 Scheme

这几种的区别。

https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/understanding-http-authentication


🔅 Bearer Authentication

Bearer 认证,只有一点,后面跟的是一个字符串。就像 前面 Basic,也是一个字符串,但它是 username:password b64token 。这里可能只要求是一个字符串,即不关心你是 base64,也不关心有没有意义吧。。。

Bearer 认证

本文要介绍的 Bearer验证 也属于HTTP协议标准验证,它随着OAuth协议而开始流行,详细定义见: RFC 6570。
October 2012

以下这段文字,让我理解出 Authentication 和 Token 两个概念。有了这两个层次的区分。才理解我的问题在哪里。

Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The name “Bearer authentication” can be understood as “give access to the bearer of this token.”

The bearer token is a cryptic string, usually generated by the server in response to a login request. The client must send this token in the Authorization header when making requests to protected resources:

1
Authorization: Bearer <token>

The Bearer authentication scheme was originally created as part of OAuth 2.0 in RFC 6750, but is sometimes also used on its own.

但有时也会单独使用。

Similarly to Basic authentication, Bearer authentication should only be used over HTTPS (SSL).

你看 OpenId 中也有用到 Bearer 的概念。。
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#jwt-bearer-sample

好处

那么使用 Bearer 验证有什么好处呢?

  • CORS: cookies + CORS 并不能跨不同的域名。而Bearer验证在任何域名下都可以使用HTTP header头部来传输用户信息。
  • 对移动端友好: 当你在一个原生平台(iOS, Android, WindowsPhone等)时,使用Cookie验证并不是一个好主意,因为你得和Cookie容器打交道,而使用Bearer验证则简单的多。
  • CSRF: 因为Bearer验证不再依赖于cookies, 也就避免了跨站请求攻击。
  • 标准:在Cookie认证中,用户未登录时,返回一个302到登录页面,这在非浏览器情况下很难处理,而Bearer验证则返回的是标准的401 challenge。


🔅 Basic Authentication Scheme

Basic Auth 是最古老的一种认证协议,在 HTTP 1.0 中,就定认了认证的协议:

最初的 RFC 在 HTTP 1.0 里。
https://tools.ietf.org/html/rfc1945#section-11.1

The ‘Basic’ HTTP Authentication Scheme
https://tools.ietf.org/html/rfc7617

Basic authentication is a simple authentication scheme built into the HTTP protocol. The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. For example, to authorize as demo / p@55w0rd the client would send:

就是用 户名:密码,b64 一下:

1
b64(zhiming:123465) = emhpbWluZzoxMjM0NjU=

use the following header field:

1
Authorization: Basic emhpbWluZzoxMjM0NjU=

https://security.stackexchange.com/questions/108662/why-is-bearer-required-before-the-token-in-authorization-header-in-a-http-re

twitter basic

这是 Twitter 的开发者中心找到的示例:

虽然不知道还有什么情景是需要这种协议的。

1
2
curl -v --compressed -u<email_address>:<password> 
"https://gnip-api.twitter.com/search/30day/accounts/<ACCOUNT-NAME>/prod/counts.json?query=from%3Atwitterdev"


🔅 其它

- Digest

后来 Firefox 对此进行了升级。这里是明文。升级为。。。密码型

- HOBA Authentication Scheme

A new scheme has been registered in the HTTP Authentication Scheme Registry as follows:

1
Authentication Scheme Name: HOBA

- HAWK Authentication Scheme

1
Authentication Hawk id="131", ts="32", nonce="243", ext="243", mac="mqWeAM8ZP6Dm+7vBBh0qxUKZftLC4iFmYjRnvJfcSAc="

- OAuth Authentication Scheme

在 OAuth 1 的版本中,定义了 OAuth 的 Auth scheme,目前 Twitter 依然在严格采用。


⭐️ ️️Bearer Tokens 种类

下面是一段很不错的介绍,不把它和 OAuth 联系在一起看,很能说明 Bearer Token 的定义,用途。

A security token with the property that any party in possession of the token (a “bearer”) can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).

The Bearer Token or Refresh token is created for you by the Authentication server. When a user authenticates your application (client) the authentication server then goes and generates for you a Bearer Token (refresh token) which you can then use to get an Access Token.

The Bearer Token is normally some kind of secret value created by the authentication server. It isn’t random; it is created based upon the user giving you access and the client your application getting access.

In order to access an API for example you need to use an Access Token. Access tokens are short lived (around an hour). You use the bearer token to get a new Access token. To get an access token you send the Authentication server this bearer token along with your client id. This way the server knows that the application using the bearer token is the same application that the bearer token was created for. Example: I can’t just take a bearer token created for your application and use it with my application it wont work because it wasn’t generated for me.

Google Refresh token looks something like this: 1/mZ1edKKACtPAb7zGlwSzvs72PvhAbGmB8K1ZrGxpcNM

copied from comment: I don’t think there are any restrictions on the bearer tokens you supply. Only thing I can think of is that its nice to allow more then one. For example a user can authenticate the application up to 30 times and the old bearer tokens will still work. oh and if one hasn’t been used for say 6 months I would remove it from your system. It’s your authentication server that will have to generate them and validate them so how it’s formatted is up to you.

关于 bear token,参看 RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage , 目前国内各大网站都是用不同的token,也没说必须使用bear token,只有twitter明确说明的是使用 bear token。

OAuth 2.0 (RFC 6749) 定义了 Client 如何取得 Access Token 的方法。Client 可以用 Access Token 以 Resource Owner 的名义来向 Resource Server 取得 Protected Resource ,例如我 (Resource Owner) 授权一個手机 App (Client) 以我 (Resource Owner) 的名义去 Facebook (Resource Server) 取得我的朋友名单 (Protected Resource)。OAuth 2.0 定义Access Token 是 Resource Server 用来认证的唯一方式,有了这个, Resource Server 就不需要再提供其他认证方式,例如账号密码。

然而在 RFC 6749 里面只定义抽象的概念,细节如 Access Token 格式、怎么传到 Resource Server ,以及 Access Token 无效时, Resource Server 怎么处理,都没有定义。所以在 RFC 6750 另外定义了 Bearer Token 的用法。Bearer Token 是一种 Access Token ,由 Authorization Server 在 Resource Owner 的允许下核发给 Client ,Resource Server 只要认在这个 Token 就可以认定 Client 已经获取 Resource Owner 的许可,不需要用密码学的方式来验证这个 Token 的真伪。关于Token 被偷走的安全性问题,另一篇再说。

Bearer Token 的格式

1
Bearer <token>

其中 XXXXXXXX 的格式 b64token ,ABNF 的定義:

1
b64token = 1*( ALPHA / DIGIT / “-” / “.” / “_” / “~” / “+” / “/” ) *”=”

写成 Regular Expression 即是:

1
/[A-Za-z0-9/-/._~/+//]+=*/

Bearer验证中的凭证称为 BEARER_TOKEN,或者是 access_token,它的颁发和验证完全由我们自己的应用程序来控制,而不依赖于系统和Web服务器,Bearer 验证的标准请求方式如下:

1
Authorization: Bearer <BEARER_TOKEN>

那 weibo, facebook, wechat 签发出来的 token 用了什么标准了吗?

custom

首先,大家是没有一个标准的。每个大厂在这一块,都是乱来的。

JWT(JSON WEB TOKEN)

上面介绍的Bearer认证,其核心便是BEARER_TOKEN,而最流行的Token编码方式便是:JSON WEB TOKEN。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JSON Web Token 是如何工作的?

在身份验证中,当用户成功地使用它们的密码登录后,一个JSON Web Token就会被服务器返回,且必须存储于客户端本地(通常存于localStorage,也可以使用cookie存储),而不是传统做法中在服务器创建一个session,并返回一个cookie。

无论何时用户想要访问一个受保护的路由或资源,客户端应该随请求一起发送JWT。JWT通常在 Authentication HTTP头部中,使用 Bearer 格式(schema)存放。HTTP头部的内容可能长这样:

1
Authorization: Bearer <token>

这是一个无状态(stateless)的认证机制,因为用户状态并未存放在服务器内存中。服务器的受保护路由会在Authentication头部中检查是否存在合法的JWT,如果存在,用户就被允许访问受保护的资源。JWT是自包含的,所以相关的信息都在里面,减少了多次查询数据库的必要。

这让你充分可以依赖无状态的数据API,甚至向下游服务发出请求。哪个域名提供API无所谓,所以跨域资源共享(CORS,Cross-Origin Resource Sharing)不会引起麻烦,因为JWT不使用cookie。

为啥不是???

1
Authorization: JWT <token>

可以参见这些
https://swagger.io/docs/specification/authentication/bearer-authentication/

Bearer 定义的是更高级的

Bearer token formatter 有很多形式:

  • JWT
  • Plain???? 之前的 oauth access_token

JWT 对数据进行了安全校验。防止被篡改。
可是像一个 access_token,什么时间会被篡改呢?

https://github.com/smilingsun/blog/issues/1

SWT (Simple Web Token)

SAML 2.0 (Security Assertion Markup Language)

https://tools.ietf.org/html/rfc7522


⭐️ 参考

以下是 Twitter 的 OAuth 使用详情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HTTP POAT
https://api.twitter.com/1.1/saved_searches/list.json

Authorization OAuth oauth_consumer_key="kxBVHz34SYKxZmdV3tjbA",oauth_token="51027998-aX4ggPDBL1KbcbrfaFpHP1WCc0rwtpDsJHXHEl8UK",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1450240474",oauth_nonce="wg3OPf",oauth_version="1.0",oauth_signature="bAbflN%2F8iyRscuagVqzNjJnDqws%3D"

POST /1.1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization:
OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com


status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21

Github

1
2
3
4
5
6
7
8
# Basic authentication
curl -u "username" https://api.github.com

# OAuth2 token (sent in a header)
curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com

# OAuth2 token (sent as a parameter)
curl https://api.github.com/?access_token=OAUTH-TOKEN

weibo

1
2
3
4
5
6
7
https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE

{
"access_token": "SlAV32hkKG",
"remind_in": 3600,
"expires_in": 3600
}

接下来所有的东西都走 access_token 了。

wechat

1
2
3
4
5
http://open.wechat.com/cgi-bin/newreadtemplate?t=overseas_open/docs/mobile/login/token#login_token

HTTP request: GET

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

jira

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
https://developer.atlassian.com/cloud/jira/software/oauth-2-jwt-bearer-token-authorization-grant-type/

POST /oauth2/token HTTP/1.1
Host: auth.atlassian.io
Accept: application/json
Content-Length: {length of the request body}
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&scope=READ+WRITE&assertion={your-signed-jwt}


HTTP/1.1 200 OK
Status: 200 OK
Content-Type: application/json; charset=utf-8
...
{
"access_token": "{your access token}",
"expires_in": {15 minutes expressed as seconds},
"token_type": "Bearer"
}

Using an access token in a request

Set the Authorization header to Bearer {your access token} and make your request:

1
2
3
4
5
GET /rest/api/latest/myself HTTP/1.1

Host: {your-registered-instance}.atlassian.net
Accept: application/json
Authorization: Bearer {your-access-token}

https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5


⭐️ ️️我的QA

🔅 OAuth 2 和 scheme

https://tools.ietf.org/html/rfc6749#section-2.3

可以看到,OAuth 2 在 Client Authentication 上,是不限定哪种 scheme 的,它在 core rfc 上,只是提到了 Basic。

于是它后来又专门出了个 RFC : 6750,来写 bearer

2.3.2. Other Authentication Methods

The authorization server MAY support any suitable HTTP authentication scheme matching its security requirements. When using other authentication methods, the authorization server MUST define a mapping between the client identifier (registration record) and authentication scheme.

Bearer 可以做到 和 OAuth 没有关系吗?

就是我自己写个验证,也用 Bearer?或者说有多个 Auth 是用 Bearer 的???

我现在的理解就是 虽然 Bearer是在 OAuth 中提出的,但也可以用在其它的验证框架中。
它只是个关键字。如何解析它,需要框架的支持。而 OAuth 就可能支持。

Bearer token 可以由多少种方式构成。

可多了。像 SWT/JWT。或者是自定义

有多少用 Bearer Scheme 的?

几大网站,没有一个!!!

为什么??

⭐️ ️️REF

https://auth0.com/blog/ten-things-you-should-know-about-tokens-and-cookies/

JSON Web Token - 深入

在上一篇,我们介绍了 Web Token 的概念,也提到了 JSON Web Token。这里,再来对 JWT 进行深入的研究,看看在实际应用中的场景。

JWT 目前的场景都是用来记录用户登录信息数据的。这很容易让人想起近些年各大平台所采用的 OAuth 2.0,这也是用来进行登录授权的,它们有什么关系和区别?


⭐️ ️️OAuth 2.0 - Tokens

OAuth 启源于平台希望共享自己的内容给第三方,和第三方共同成长。但 OAuth 协议本身并未对 token 进行约定。

可以说 JWT 和 OAuth 协议本身并不在一个层面上,JWT 负责数据传输中的标准格式,而 OAuth 则负责签发证书和负责证书有效时长等。

但 OAuth 使用者比较要传递 token 啊,token 长什么样,如何定义?

这就有了 Bearer Token,JWT 实际上是在和它进行对比。

🔅 OAuth Bearer Token 举例

比如说这是一个 password 类型的 grant_type

1
2
3
grant_type=password&client_id=3MVG9lKcPoNINVBIPJjdw1J9LLM82Hn
FVVX19KY1uA5mu0QqEWhqKpoW3svG3XHrXDiCQjK1mdgAvhCscA9GE&client_secret=
1955279925675241571&username=testuser%40salesforce.com&password=mypassword

这是它的返回值:

1
2
3
4
5
6
7
8
{
"id":"https://login.salesforce.com/id/00Dx0000000BV7z/005x00000012Q9P",
"issued_at":"1278448832702”,
"instance_url":"https://yourInstance.salesforce.com/",
"signature":"0CmxinZir53Yex7nE0TD+zMpvIWYGb/bdJh6XfOH6EQ=“,
"access_token”: "00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs”,
"token_type":”Bearer"
}

注意到,返回值中有 access_token 和 token_type=”Bearer”

🔅 常见 OAuth Token 方案:

其中的 Bearer 就是 OAuth 扩展协议 RF6750 加入的对 Token 具体的约定,当然了,结合上两讲的 Web Token,这里还会有其它类型的方案,像 RFC7523 的 JWT Profile 和 RFC7522 的 SAML2.0.

🔅 OAuth 2.0 - Bearer token

OAuth 2 里,其实并未说明 token 的 DSF,后来才加入的 Bearer Token

https://tools.ietf.org/html/rfc6750

Bearer Token 的格式 JWTs 传递 Toke 就是利用新定义的

1
Bearer XXXXXXXX

其中 XXXXXXXX 的格式為 b64token ,ABNF 的定義:

1
b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="

寫成 Regular Expression 即是:

1
/[A-Za-z0-9\-\._~\+\/]+=*/

Authorization: Bearer

🔅 OAuth 2.0 - SAML Bearer Token

🔅 OAuth 2.0 - JWT Bearer Token

JSON Web Token (JWT):RFC7519 - JSON Web Token (JWT),该协议定义了一种自包含Token类型及完整性校验方法;RFC7797 - JSON Web Signature (JWS) Unencoded Payload Option,该协议对RFC7519的更新

JWT 是 access_token 的格式的一种实现。

OAuth 2.0 用户名密码流程


⭐️ ️️微服务下的 JWT

OAuth2 负责签发 Token,而 Edge Service 做自己的 JWT,在 JWT 的 payload 中放入自己的数据。

当然 OAuth provider 也可以用 JWT,如果他们需要带上一些附属信息的话,而 Edge Service 可以根据自己的情况,做自己的 JWT。

JSON Web Token - 入门

在上一篇,我们介绍了 Web Token 的概念,也提到了 JSON Web Token。这里,再来对 JWT 进行深入的研究。


⭐️ ️️JWT 介绍

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

特点

  • 体积小,因而传输速度快
  • 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
  • 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化。
  • 支持跨域验证,可以应用于单点登录。


⭐️ ️️JWT 格式

JWT 使用了 JSON 格式,由三个部分组成:

  • Header
  • Payload
  • Signature

看起来像这样:

1
xxxxx.yyyyy.zzzzz

🔅 Header

头部由两部分信息:

  • 声明类型,这里是 JWT
  • 声明加密的算法 通常直接使用 HMAC SHA256/RSA
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

将这部分值进行 Base64 得到 JWT 第一部分: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

🔅 Payload

Payload 是存放信息的地方,它有也一些标准字段的定义,但都不是强制的。

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明

JWT 标准所定义了五个值

  • iss: 该JWT的签发者
  • sub: 该JWT所面向的用户
  • aud: 接收该JWT的一方
  • exp(expires): 什么时候过期,这里是一个Unix时间戳
  • iat(issued at): 在什么时候签发的
  • nbf: 定义在什么时间之前,该jwt都是不可用的
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

这里我们不使用标准,自行放一些数据:

1
2
3
4
5
{
"id": "1234567890",
"name": "Zhiming Jiang",
"handsome": true
}

最后将这部分值进行 Base64 得到 JWT 第二部分: eyJpZCI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiWmhpbWluZyBKaWFuZyIsImhhbmRzb21lIjp0cnVlfQ

🔅 Signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

它由前两部分的 Base64 值合在一起,加上一个 salt 什值,再由 Header 中的加密方式对这个字符串进行加密生成这一部分: yHFP8ABW2Oxk3eH1C8ODyPf6UO84yP7zbXOaVY5boUE

🔅 最终

1
base64(Header) + '.' + base64(Claims) + '.' + base64(Signature)

最终,就变成这样的一个字符串:

很小,这方便了它的传输,可以放在 Header 里,也可以放在 QueryString 中。


⭐️ ️️应用

一般是在请求头里加入Authorization,并加上Bearer标注:

1
2
3
headers: {
'Authorization': 'Bearer ' + token
}

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:


⭐️ ️️总结

JSON 现在是事实的 DSF 标准了,所有的主流语言都支持 序列化/反序列化,所以使用 JSON 是一个不错的选择。

Payload 部分可以自由存储,方便扩展。

JWT 相对比较小巧,便于传输。

保证了数据不会被篡改,对安全性有保证

安全

依然要使用 HTTPS 协议来进行安全保证

Payload 部分不应该放敏感信息。

不要泄露 Secret 信息。

Web Token

最近在一个新项目中,要做鉴权的事宜,于是 Token 的话题又被拿了出来,JWT 是一个很火的名字,但事实上,并没有人很了解这个东西,于是我准备自己来解释下。


⭐️ ️️如何传递用户的安全信息

HTTP 的无状态特性

在 Web 应用中,经常要处理的一类问题是,验证。如何记住当前是哪一个用户是一件很麻烦的事情。

这是因为 HTTP 协议是短连接,每次请求都是无状态的,这使得每次客户端和服务器都需要在会话时,带上 我是谁 这个信息。

用户安全信息常用有两种方式:

  • Cookie
  • Token

HTTP 协议 中有关于 Cookie 的定义。人们可以通过将数据写入 cookie ,每次 HTTP 请求都带上这段信息的方式来进行信息传递。

用户安全信息不属于业务的部分,被放在 Cookie 里再适合不过了。

通常,在 Cookie 里记录着用户的安全信息,这些信息包括 token,或者一些用户的数据,以减少服务端每次查询的压力。

但浏览器为了安全考虑,通常对 Cookie 做了严格的限制,像不得跨域携带等,在 API 时代,Cookie 更加的不方便使用。

🔅 HTTP Request

于是,一个解题的新思路就来了,能不能把数据放在 HTTP 的请求头中,像 Header 或 QueryString 中。

我们把这种方式叫做 Web Token。

好处显而易见,不再有跨域的麻烦,而且是 HTTP 标准,处理起来也很方便。


⭐️ Web Tokens

我们定义了 Web Token 在请求体中的位置,但 Web Token 的格式,还没有做要求。理论上一个请求体上的内容,如果仅是自己在用的话,怎么写都可以,但当各方站点需要一起通讯的话,或者为了方便交流的原因,也会形成一个共用标准的。

于是就有以下几种常见标准:

  • JSON Web Tokens (JWT)
  • Simple Web Tokens (SWT)
  • Security Assertion Markup Language Tokens (SAML)

对比。

SWT 定义了文本的格式,有几个字段的标准,最终生成的是这些字符串的 base64
而 SAML 则是使用 XML 格式
JWT 则使用 JSON,除此之外,JWT 还定义了一些字段的要求

🔅 SWT

SWT 只在 MSDN 和 JWT 的说明文档上见到,可见使用不是特别的广泛。

这是它的定义部分:

1
2
3
4
Issuer = issuer.example.com
ExpiresOn = 1/1/2010, Midnight
com.example.group = gold
over18 = true

Base64 后的结果

1
N4QeKa3c062VBjnVK6fb+rnwURkcwGXh7EoNK34n0uM=

🔅 SAML 2.0

除了 XML 数据格式外,还定义了以下标准字段:

  • saml:Issuer
  • ds:Signature
  • saml:Subject
  • saml:Conditions
  • saml:AuthnStatement
  • saml:AttributeStatement

可以看下 Wiki 上的定义内容,特点就是冗长,因为在长了,这里也就不再复制了。

🔅 JWT

JWT 是目前最火的 Web Token 协议,已经被加入 RFC 中了。这得益于 JSON 的优点,广泛的平台和比较轻巧的格式。

简单的说,它使用了 JSON 格式,由三个部分组成:

  • Header
  • Payload
  • Signature

头部由两部分信息:

  • 声明类型,这里是 JWT
  • 声明加密的算法 通常直接使用 HMAC/SHA256
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

将这部分值进行 Base64 得到 JWT 第一部分: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Payload

Payload 是存放信息的地方,它有也一些标准字段的定义,但都不是强制的。

1
2
3
4
5
{
"id": "1234567890",
"name": "John Doe",
"admin": true
}

最后将这部分值进行 Base64 得到 JWT 第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

Signature

它由前两部分的 Base64 值合在一起,再由 Header 中的加密方式生成这一部分:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

最终,就变成这样的一个字符串:

很小,这方便了它的传输,可以放在 Header 里,也可以放在 QueryString 中。


⭐️ 后续

这里我们介绍了如何传递用户安全数据的方式,然后对 Web Token 的几种常见标准进行了介绍。

作为主流当红的 JWT,接下来我们会再花笔墨去重点介绍下。