JSON Web Token 的简单介绍

作者: caixw
修改时间:

JSON Web Token 是一种基于 Token 的认证授权机制,本文对其实现原理做个简单的介绍。若要查看详细的相关信息,可以访问 https://jwt.io/,该网站还有各类语言实现的库,当然你也可以直接阅读其官方的标准文档

结构

一个 JWT 由三段 base64 统码的字符串通过点号(.)组合而成,看起来像这个样子:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjYWl4dyIsInN1YiI6InRlc3Qgand0IiwiZXhwIjoxNDYyNDU5MzUxLCJ1aWQiOjEwMjQsInR5cGUiOjF9.y2TytagRDbHI-uQrsMqae2FD5OxqAAFbKp-8rYB2k0I,其中前两段是两个 JSON 结构体的 base64 编码,第三段为对前两段编码的再次编码,起到验证的作用,大致算法可以理解成以下样子:token := base64(header) + base64(payload) + base64(signature)

Header 为一个 JSON 结构,定义了 JWT 使用的 Token 类型和加密方法,经过 base64 编码后组成 JWT 的第一部分。其结构如下:

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

对其进行 base64 编码之后,得到以下字符串:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Payload(Claims)

Payload 是 JWT 的核心部分,包含了描述 JWT 的重要信息。也是一个 JSON 数据,字段可以根据用户的需求自定义,只要保证字段名唯一就行。当然官方也定义了一些可能会经常用到的内容,所有这些字段都是可选的,Payload 的值是没有加密的,所以不要在里面包含一些敏感信息。

一些官方定义的字段:

  • iss:给谁的,可以是一个 URL;
  • sub:主题;
  • exp:过期时间。
1{
2    "iss": "caixw",
3    "sub": "test jwt",
4    "exp": 1462459351,
5    "uid": 1024,
6    "type":1
7}

编码之后,会得到:eyJpc3MiOiJjYWl4dyIsInN1YiI6InRlc3Qgand0IiwiZXhwIjoxNDYyNDU5MzUxLCJ1aWQiOjEwMjQsInR5cGUiOjF9

Signature

Signature 是对前两段编码的一个签名验证,用于验证 Token 是否有效果,是否被篡改过等。其大致的算法为:将前两段编码通过点号(.)拼接起来,然后对其进行一次 base64 编码。

1header := base64(headerJSON)
2payload := base64(payloadJSON)
3content := header + "." + payload
4signature := base64(content)

计算之后会得到以下字符串:y2TytagRDbHI-uQrsMqae2FD5OxqAAFbKp-8rYB2k0I

之后将三段字符串用点号(.)进行拼接,就得到一个完整的 JWT 了:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjYWl4dyIsInN1YiI6InRlc3Qgand0IiwiZXhwIjoxNDYyNDU5MzUxLCJ1aWQiOjEwMjQsInR5cGUiOjF9.y2TytagRDbHI-uQrsMqae2FD5OxqAAFbKp-8rYB2k0I

JWT 是 URL Safe 的,所以对进行 base64 编码的时候,应该使用 base64.RawURLEncoding 对其进行编码,而不是 base64.RawStdEncoding

简单的实现

这是一个简单的 Go 实现版本,其它语言也大同小异。

 1// Copyright 2016 by caixw, All rights reserved.
 2// Use of this source code is governed by a MIT
 3// license that can be found in the LICENSE file.
 4
 5package main
 6
 7import (
 8    "crypto/hmac"
 9    "crypto/sha256"
10    "encoding/base64"
11    "encoding/json"
12    "errors"
13    "fmt"
14    "strings"
15)
16
17const secret = "a;/dkfwer"
18
19type Header struct {
20    Type      string `json:"typ"`
21    Algorithm string `json:"alg"`
22}
23
24func (h *Header) Base64() string {
25    data, err := json.Marshal(h)
26    if err != nil {
27        panic(err)
28    }
29
30    return base64.RawURLEncoding.EncodeToString(data)
31}
32
33type Claims struct {
34    Issuer     string `json:"iss,omitempty"`
35    Subject    string `json:"sub,omitempty"`
36    Expiration int64  `json:"exp,omitempty"`
37    UID        int64  `json:"uid"`
38    Type       int64  `json:"type"`
39}
40
41func (c *Claims) Base64() string {
42    data, err := json.Marshal(c)
43    if err != nil {
44        panic(err)
45    }
46
47    return base64.RawURLEncoding.EncodeToString(data)
48}
49
50// 根据算法生成签名
51func Signature(h *Header, c *Claims) (string, error) {
52    content := h.Base64() + "." + c.Base64()
53
54    switch strings.ToLower(h.Algorithm) {
55    case "hs256":
56        h := hmac.New(sha256.New, []byte(secret))
57        h.Write([]byte(content))
58        return content + "." + base64.RawURLEncoding.EncodeToString(h.Sum(nil)), nil
59    default:
60        return "", errors.New("不支持的加密算法")
61    }
62}
63
64func main() {
65    h := &Header{
66        Type:      "JWT",
67        Algorithm: "HS256",
68    }
69
70    c := &Claims{
71        Issuer:     "caixw",
72        Subject:    "test jwt",
73        Expiration: 1462459351,
74        UID:        1024,
75        Type:       1,
76    }
77
78    token, err := Signature(h, c)
79    if err != nil {
80        panic(err)
81    }
82    fmt.Println(token)
83}