JSON Web Token 的简单介绍
JSON Web Token 是一种基于 Token 的认证授权机制,本文对其实现原理做个简单的介绍。若要查看详细的相关信息,可以访问 https://jwt.io/,该网站还有各类语言实现的库,当然你也可以直接阅读其官方的标准文档。
结构
一个 JWT 由三段 base64 统码的字符串通过点号(.)组合而成,看起来像这个样子:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjYWl4dyIsInN1YiI6InRlc3Qgand0IiwiZXhwIjoxNDYyNDU5MzUxLCJ1aWQiOjEwMjQsInR5cGUiOjF9.y2TytagRDbHI-uQrsMqae2FD5OxqAAFbKp-8rYB2k0I
,其中前两段是两个 JSON 结构体的 base64 编码,第三段为对前两段编码的再次编码,起到验证的作用,大致算法可以理解成以下样子:token := base64(header) + base64(payload) + base64(signature)
。
header
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 := sign(content, secret)
计算之后会得到以下字符串:y2TytagRDbHI-uQrsMqae2FD5OxqAAFbKp-8rYB2k0I
。
之后将三段字符串用点号(.)进行拼接,就得到一个完整的 JWT 了:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjYWl4dyIsInN1YiI6InRlc3Qgand0IiwiZXhwIjoxNDYyNDU5MzUxLCJ1aWQiOjEwMjQsInR5cGUiOjF9.y2TytagRDbHI-uQrsMqae2FD5OxqAAFbKp-8rYB2k0I
。
JWT 是 URL Safe 的,所以对进行 base64 编码的时候,应该使用 base64.RawURLEncoding
对其进行编码,而不是 base64.RawStdEncoding
。
sign
为加密算法,即 Header.alg 指定的值,secret
为算法所需的密钥,仅服务器持有。
简单的实现
这是一个简单的 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 + "." + string(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}