Zhuang's Diary

言之有物,持之以恒

Haproxy配置为TCP模式,通常会配置健康检查所代理服务的端口。

1
2
3
4
frontend main *:6000
default_backend app
backend app
server app1 192.168.2.10:8080 check

Haproxy的健康检查机制是利用TCP的三次握手。
1、首先由Haproxy向代理的服务器发起SYN握手协商,默认是与代理的端口建立链接,比如说8080。
2、等待代理服务器确认第一次SYN,并响应ACK,与发起SYN的第二次握手。
3、Haproxy收到确认ACK之后,会向代理服务器发送TCP链接重置的报文,已经确认代理的服务器健康。

参考链接:

https://www.jianshu.com/p/c9f6d55288c0

OpenPGP Armor

OpenPGP是使用最广泛的电子邮件加密标准。它由Internet工程任务组(IETF)的OpenPGP工作组定义为RFC 4880中的建议标准.OpenPGP最初源自由Phil Zimmermann创建的PGP软件。

虽然OpenPGP的主要目的是端到端加密电子邮件通信,但它也用于加密消息传递和其他用例,如密码管理器。

OpenPGP的加密消息,签名证书和密钥的基本描述是八位的字节流。为了通过不能保障安全的网络通道传输OpenPGP的二进制八位字节,需要编码为可打印的二进制字符。OpenPGP提供将原始8位二进制八位字节流转换为可打印ASCII字符流,称为Radix-64编码或ASCII Armor。

ASCII Armor是OpenPGP的可选功能。当OpenPGP将数据编码为ASCII Armor时,它会在Radix-64编码数据中放置特定的Header。OpenPGP可以使用ASCII Armor来保护原始二进制数据。OpenPGP通过使用Header告知用户在ASCII Armor中编码了什么类型的数据。

ASCII Armor的数据结构如下:

  • Armor标题行,匹配数据类型
  • Armor Headers
  • A Blank(零长度或仅包含空格) Line
  • The ASCII-Armored data
  • An Armor Checksum
  • The Armor Tail,取决于护甲标题线

具体示例:

1
2
3
4
5
6
7
8
9
10
-----BEGIN PGP MESSAGE-----

Version: OpenPrivacy 0.99


yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzSvBSFjNSiVHsuAA==

=njUN

-----END PGP MESSAGE-----

golang.org/x/crypto/openpgp/armor 代码分析:

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
// Encode 返回一个 WriteCloser,它将对 写入的数据进行编码
// Encode returns a WriteCloser which will encode the data written to it in
// OpenPGP Armor.
func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
bType := []byte(blockType)
err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
if err != nil {
return
}

for k, v := range headers {
err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
if err != nil {
return
}
}

_, err = out.Write(newline)
if err != nil {
return
}

e := &encoding{
out: out,
breaker: newLineBreaker(out, 64),
crc: crc24Init,
blockType: bType,
}
e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
return e, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 将返回值进一步封装为字符串
func EncodeArmor(blockType string, headers map[string]string, data []byte) string {
buf := new(bytes.Buffer)
w, err := armor.Encode(buf, blockType, headers)
if err != nil {
panic(fmt.Errorf("could not encode ascii armor: %s", err))
}
_, err = w.Write(data)
if err != nil {
panic(fmt.Errorf("could not encode ascii armor: %s", err))
}
err = w.Close()
if err != nil {
panic(fmt.Errorf("could not encode ascii armor: %s", err))
}
return buf.String()
}
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
// 实际使用中将公钥导出为 ASCII Armor 格式
// ExportPubKey returns public keys in ASCII armored format.
func armorPubKeyBytes(bz []byte) string {
return armorBytes(bz, blockTypePubKey)
}

func armorBytes(bz []byte, blockType string) string {
header := map[string]string{
"type": "Info",
"version": "0.0.0",
}
return armor.EncodeArmor(blockType, header, bz)
}

func ExportPubKey(name string) (armor string, err error) {
bz := Get(infoKey(name))
if bz == nil {
return "", fmt.Errorf("no key to export with name %s", name)
}
info, err := readInfo(bz)
if err != nil {
return
}
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil
}

在 CovalentChain 公钥、私钥的导入导出和传输等情况,使用 ASCII Armor 编码格式非常合适。另外,在私钥情况下,通常都需要在加密后,再进行编码处理。

参考链接:

  1. https://tools.ietf.org/html/rfc4880
  2. https://www.openpgp.org/
  3. golang.org/x/crypto/openpgp/armor

解决什么问题?

JavaScript 从诞生起到现在已经变成最流行的编程语言,这背后正是 Web 的发展所推动的。Web 应用变得更多更复杂,但这也渐渐暴露出了 JavaScript 的问题:

  1. 语法太灵活导致开发大型 Web 项目困难;
  2. 性能不能满足一些场景的需要。
  3. 针对以上两点缺陷,近年来出现了一些 JS 的代替语言,例如:
  • 微软的 TypeScript 通过为 JS 加入静态类型检查来改进 JS 松散的语法,提升代码健壮性;
  • 谷歌的 Dart 则是为浏览器引入新的虚拟机去直接运行 Dart 程序以提升性能;
  • 火狐的 asm.js 则是取 JS 的子集,JS 引擎针对 asm.js 做性能优化。

以上尝试各有优缺点,其中:

  • TypeScript 只是解决了 JS 语法松散的问题,最后还是需要编译成 JS 去运行,对性能没有提升;
  • Dart 只能在 Chrome 预览版中运行,无主流浏览器支持,用 Dart 开发的人不多;
  • asm.js 语法太简单、有很大限制,开发效率低。

三大浏览器巨头分别提出了自己的解决方案,互不兼容,这违背了 Web 的宗旨; 是技术的规范统一让 Web 走到了今天,因此形成一套新的规范去解决 JS 所面临的问题迫在眉睫。

于是 WebAssembly 诞生了,WebAssembly 是一种新的字节码格式,主流浏览器都已经支持 WebAssembly。和 JS 需要解释执行不同的是,WebAssembly 字节码和底层机器码很相似可快速装载运行,因此性能相对于 JS 解释执行大大提升。 也就是说 WebAssembly 并不是一门编程语言,而是一份字节码标准,需要用高级编程语言编译出字节码放到 WebAssembly 虚拟机中才能运行, 浏览器厂商需要做的就是根据 WebAssembly 规范实现虚拟机。

WebAssembly 原理

要搞懂 WebAssembly 的原理,需要先搞懂计算机的运行原理。电子计算机都是由电子元件组成,为了方便处理电子元件只存在开闭两种状态,对应着 0 和 1,也就是说计算机只认识 0 和 1,数据和逻辑都需要由 0 和 1 表示,也就是可以直接装载到计算机中运行的机器码。 机器码可读性极差,因此人们通过高级语言 C、C++、Rust、Go 等编写再编译成机器码。

由于不同的计算机 CPU 架构不同,机器码标准也有所差别,常见的 CPU 架构包括 x86、AMD64、ARM,因此在由高级编程语言编译成可自行代码时需要指定目标架构。

WebAssembly 字节码是一种抹平了不同 CPU 架构的机器码,WebAssembly 字节码不能直接在任何一种 CPU 架构上运行,但由于非常接近机器码,可以非常快的被翻译为对应架构的机器码,因此 WebAssembly 运行速度和机器码接近,这听上去非常像 Java 字节码。

相对于 JS,WebAssembly 有如下优点:

  1. 体积小:由于浏览器运行时只加载编译成的字节码,一样的逻辑比用字符串描述的 JS 文件体积要小很多;
  2. 加载快:由于文件体积小,再加上无需解释执行,WebAssembly 能更快的加载并实例化,减少运行前的等待时间;
  3. 兼容性问题少:WebAssembly 是非常底层的字节码规范,制订好后很少变动,就算以后发生变化,也只需在从高级语言编译成字节码过程中做兼容。可能出现兼容性问题的地方在于 JS 和 WebAssembly 桥接的 JS 接口。

每个高级语言都去实现源码到不同平台的机器码的转换工作是重复的,高级语言只需要生成底层虚拟机(LLVM)认识的中间语言(LLVM IR),LLVM 能实现:

  • LLVM IR 到不同 CPU 架构机器码的生成;
  • 机器码编译时性能和大小优化。

除此之外 LLVM 还实现了 LLVM IR 到 WebAssembly 字节码的编译功能,也就是说只要高级语言能转换成 LLVM IR,就能被编译成 WebAssembly 字节码,目前能编译成 WebAssembly 字节码的高级语言有:

  • AssemblyScript:语法和 TypeScript 一致,对前端来说学习成本低,为前端编写 WebAssembly 最佳选择
  • c\c++:官方推荐的方式;
  • Rust:语法复杂、学习成本高,对前端来说可能会不适应;
  • Kotlin:语法和 Java、JS 相似,语言学习成本低;
  • Golang:语法简单学习成本低。但对 WebAssembly 的支持还处于未正式发布阶段。

通常负责把高级语言翻译到 LLVM IR 的部分叫做编译器前端,把 LLVM IR 编译成各架构 CPU 对应机器码的部分叫做编译器后端; 现在越来越多的高级编程语言选择 LLVM 作为后端,高级语言只需专注于如何提供开发效率更高的语法同时保持翻译到 LLVM IR 的程序执行性能。

编写 WebAssembly

接下来详细介绍如何使用 AssemblyScript 来编写 WebAssembly,实现斐波那契序列的计算。 用 TypeScript 实现斐波那契序列计算的模块 f.ts 如下:

1
2
3
4
5
6
export function f(x: i32): i32 {
if (x === 1 || x === 2) {
return 1;
}
return f(x - 1) + f(x - 2)
}

在按照 AssemblyScript 提供的安装教程成功安装后, 再通过

1
asc f.ts -o f.wasm

就能把以上代码编译成可运行的 WebAssembly 模块。

为了加载并执行编译出的 f.wasm 模块,需要通过 JS 去加载并调用模块上的 f 函数,为此需要以下 JS 代码:

1
2
3
4
5
6
fetch('f.wasm') // 网络加载 f.wasm 文件
.then(res => res.arrayBuffer()) // 转成 ArrayBuffer
.then(WebAssembly.instantiate) // 编译为当前 CPU 架构的机器码 + 实例化
.then(mod => { // 调用模块实例上的 f 函数计算
console.log(mod.instance.f(50));
});

以上代码中出现了一个新的内置类型 i32,这是 AssemblyScript 在 TypeScript 的基础上内置的类型。AssemblyScript 和 TypeScript 有细微区别,AssemblyScript 是 TypeScript 的子集,为了方便编译成 WebAssembly 在 TypeScript 的基础上加了更严格的类型限制,区别如下:

  • 比 TypeScript 多了很多更细致的内置类型,以优化性能和内存占用;
  • 不能使用 any 和 undefined 类型,以及枚举类型;
  • 可空类型的变量必须是引用类型,而不能是基本数据类型如 string、number、boolean;
  • 函数中的可选参数必须提供默认值,函数必须有返回类型,无返回值的函数返回类型需要是 void;
  • 不能使用 JS 环境中的内置函数,只能使用 AssemblyScript 提供的内置函数。

总体来说 AssemblyScript 比 TypeScript 又多了很多限制,编写起来会觉得局限性很大; 用 AssemblyScript 来写 WebAssembly 经常会出现 tsc 编译通过但运行 WebAssembly 时出错的情况,这很可能就是你没有遵守以上限制导致的;但 AssemblyScript 通过修改 TypeScript 编译器默认配置能在编译阶段找出大多错误。

AssemblyScript 的实现原理其实也借助了 LLVM,它通过 TypeScript 编译器把 TS 源码解析成 AST,再把 AST 翻译成 IR,再通过 LLVM 编译成 WebAssembly 字节码实现; 上面提到的各种限制都是为了方便把 AST 转换成 LLVM IR。

为什么选 AssemblyScript 作为 WebAssembly 开发语言

AssemblyScript 相对于 C、Rust 等其它语言去写 WebAssembly 而言,好处除了对前端来说无额外新语言学习成本外,还有对于不支持 WebAssembly 的浏览器,可以通过 TypeScript 编译器编译成可正常执行的 JS 代码,从而实现从 JS 到 WebAssembly 的平滑迁移。

接入 Webpack 构建

任何新的 Web 开发技术都少不了构建流程,为了提供一套流畅的 WebAssembly 开发流程,接下来介绍接入 Webpack 具体步骤。

  1. 安装以下依赖,以便让 TS 源码被 AssemblyScript 编译成 WebAssembly。
1
2
3
4
5
6
7
8
9
{
"devDependencies": {
"assemblyscript": "github:AssemblyScript/assemblyscript",
"assemblyscript-typescript-loader": "^1.3.2",
"typescript": "^2.8.1",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.10.1"
}
}
  1. 修改 webpack.config.js,加入 loader:
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: 'assemblyscript-typescript-loader',
options: {
sourceMap: true,
}
}
]
},
};
  1. 修改 TypeScript 编译器配置 tsconfig.json,以便让 TypeScript 编译器能支持 AssemblyScript 中引入的内置类型和函数。
1
2
3
4
5
6
{
"extends": "../../node_modules/assemblyscript/std/portable.json",
"include": [
"./**/*.ts"
]
}
  1. 配置直接继承自 assemblyscript 内置的配置文件。

WebAssembly 相关文件格式

前面提到了 WebAssembly 的二进制文件格式 wasm,这种格式的文件人眼无法阅读,为了阅读 WebAssembly 文件的逻辑,还有一种文本格式叫 wast; 以前面讲到的计算斐波那契序列的模块为例,对应的 wast 文件如下:

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
func $src/asm/module/f (param f64) (result f64)
(local i32)
get_local 0
f64.const 1
f64.eq
tee_local 1
if i32
get_local 1
else
get_local 0
f64.const 2
f64.eq
end
i32.const 1
i32.and
if
f64.const 1
return
end
get_local 0
f64.const 1
f64.sub
call 0
get_local 0
f64.const 2
f64.sub
call 0
f64.add
end

这和汇编语言非常像,里面的 f64 是数据类型,f64.eq f64.sub f64.add 则是 CPU 指令。

为了把二进制文件格式 wasm 转换成人眼可见的 wast 文本,需要安装 WebAssembly 二进制工具箱WABT, 在 Mac 系统下可通过 brew install WABT 安装,安装成功后可以通过命令 wasm2wast f.wasm 获得 wast;除此之外还可以通过 wast2wasm f.wast -o f.wasm 逆向转换回去。

WebAssembly 相关工具

除了前面提到的 WebAssembly 二进制工具箱,WebAssembly 社区还有以下常用工具:

  • Emscripten: 能把 C、C++代码转换成 wasm、asm.js;
  • Binaryen: 提供更简洁的 IR,把 IR 转换成 wasm,并且提供 wasm 的编译时优化、wasm 虚拟机,wasm 压缩等功能,前面提到的 AssemblyScript 就是基于它。

WebAssembly JS API

目前 WebAssembly 只能通过 JS 去加载和执行,但未来在浏览器中可以通过像加载 JS 那样 去加载和执行 WebAssembly,下面来详细介绍如何用 JS 调 WebAssembly。

JS 调 WebAssembly 分为 3 大步:加载字节码 > 编译字节码 > 实例化,获取到 WebAssembly 实例后就可以通过 JS 去调用了,以上 3 步具体的操作是:

  1. 对于浏览器可以通过网络请求去加载字节码,对于 Nodejs 可以通过 fs 模块读取字节码文件;
  2. 在获取到字节码后都需要转换成 ArrayBuffer 后才能被编译,通过 WebAssembly 通过的 JS API WebAssembly.compile 编译后会通过 Promise resolve 一个 WebAssembly.Module,这个 module 是不能直接被调用的需要;
  3. 在获取到 module 后需要通过 WebAssembly.Instance API 去实例化 module,获取到 Instance 后就可以像使用 JS 模块一个调用了。
    其中的第 2、3 步可以合并一步完成,前面提到的 WebAssembly.instantiate 就做了这两个事情。
1
2
3
WebAssembly.instantiate(bytes).then(mod=>{
mod.instance.f(50);
})

直接执行 wasm 二进制文件

前面提到的 Binaryen 提供了在命令行中直接执行 wasm 二进制文件的工具,在 Mac 系统下通过 brew install binaryen 安装成功后,通过 wasm-shell f.wasm 文件即可直接运行。

在 Node.js 中运行

目前 V8 JS 引擎已经添加了对 WebAssembly 的支持,Chrome 和 Node.js 都采用了 V8 作为引擎,因此 WebAssembly 也可以运行在 Node.js 环境中;

V8 JS 引擎在运行 WebAssembly 时,WebAssembly 和 JS 是在同一个虚拟机中执行,而不是 WebAssembly 在一个单独的虚拟机中运行,这样方便实现 JS 和 WebAssembly 之间的相互调用。

要让上面的例子在 Node.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
const fs = require('fs');

function toUint8Array(buf) {
var u = new Uint8Array(buf.length);
for (var i = 0; i < buf.length; ++i) {
u[i] = buf[i];
}
return u;
}

function loadWebAssembly(filename, imports) {
// 读取 wasm 文件,并转换成 byte 数组
const buffer = toUint8Array(fs.readFileSync(filename));
// 编译 wasm 字节码到机器码
return WebAssembly.compile(buffer)
.then(module => {
// 实例化模块
return new WebAssembly.Instance(module, imports)
})
}

loadWebAssembly('../temp/assembly/module.wasm')
.then(instance => {
// 调用 f 函数计算
console.log(instance.exports.f(10))
});

在 Nodejs 环境中运行 WebAssembly 的意义其实不大,原因在于 Nodejs 支持运行原生模块,而原生模块的性能比 WebAssembly 要好。 如果你是通过 C、Rust 去编写 WebAssembly,你可以直接编译成 Nodejs 可以调用的原生模块。

WebAssembly 实际应用

从上面的内容可见 WebAssembly 主要是为了解决 JS 的性能瓶颈,也就是说 WebAssembly 适合用于需要大量计算的场景,例如:

  1. 在浏览器中处理音视频,flv.js 用 WebAssembly 重写后性能会有很大提升;
  2. React 的 dom diff 中涉及到大量计算,用 WebAssembly 重写 React 核心模块能提升性能。Safari 浏览器使用的 JS 引擎 JavaScriptCore 也已经支持 WebAssembly,RN 应用性能也能提升;
    突破大型 3D 网页游戏性能瓶颈,白鹭引擎已经开始探索用 WebAssembly

参考资源

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。

一、跨域认证的问题

互联网服务离不开用户认证。一般流程是下面这样。

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。
  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

二、JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

三、JWT 的数据结构

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

写成一行,就是下面的样子。

1
Header.Payload.Signature

下面依次介绍这三个部分。

3.1 Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

3.2 Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

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

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个 JSON 对象也要使用 Base64URL 算法转成字符串。

3.3 Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

3.4 Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

四、JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

1
Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

五、JWT 的几个特点

  1. JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  2. JWT 不加密的情况下,不能将秘密数据写入 JWT。
  3. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  4. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  5. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  6. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

参考链接: https://jwt.io/

  1. Α α alpha a:lf 阿尔法 角度;系数
  2. Β β beta bet 贝塔 磁通系数;角度;系数
  3. Γ γ gamma ga:m 伽马 电导系数(小写)
  4. Δ δ delta delt 德尔塔 变动;密度;屈光度
  5. Ε ε epsilon 伊普西龙 对数之基数
  6. Ζ ζ zeta zat 截塔 系数;方位角;阻抗;相对粘度;原子序数
  7. Η η eta eit 艾塔 磁滞系数;效率(小写)
  8. Θ θ thet θit 西塔 温度;相位角
  9. Ι ι iot aiot 约塔 微小,一点儿
  10. Κ κ kappa kap 卡帕 介质常数
  11. ∧ λ lambda lambd 兰布达波长(小写);体积
  12. Μ μ mu mju 缪 磁导系数;微(千分之一);放大因数(小写)
  13. Ν ν nu nju 纽 磁阻系数
  14. Ξ ξ xi ksi 克西
  15. Ο ο omicron 奥密克戎
  16. ∏ π pi pai 派 圆周率=圆周÷直径=3.1416
  17. Ρ ρ rho rou 肉 电阻系数(小写)
  18. ∑ σ sigma 西格马 总和(大写),表面密度;跨导(小写)
  19. Τ τ tau tau 套 时间常数
  20. Υ υ upsilon 宇普西龙 位移
  21. Φ φ phi fai 佛爱 磁通;角
  22. Χ χ chi phai 西
  23. Ψ ψ psi psai 普西 角速;介质电通量(静电力线);角
  24. Ω ω omega o`miga 欧米伽 欧姆(大写);角速(小写);角