JWT令牌验证

jwt


一.JWT简介

官方仓库:golang-jwt/jwt: Community maintained clone of https://github.com/dgrijalva/jwt-go

官方文档:jwt package - github.com/golang-jwt/jwt/v4 - Go Packages

一、JWT 是什么 & 工作原理

JWT(JSON Web Token) 是一种开放标准(RFC 7519),用于在客户端和服务端之间安全地传递 JSON 格式的信息。

结构:
JWT 分为三部分,用 . 连接:

1
Header.Payload.Signature
  1. Header(头部)

    • 声明类型(JWT)和算法(HS256、RS256 等)。
    1
    2
    3
    4
    {
    "alg": "HS256",
    "typ": "JWT"
    }
  2. Payload(负载)

    • 存放业务信息,比如用户 ID、用户名、过期时间等。
    1
    2
    3
    4
    5
    {
    "user_id": 123,
    "username": "tom",
    "exp": 1695200400
    }
  3. Signature(签名)

    • 防止篡改,用 Header + Payload + 秘钥(secret)通过算法签名。
    • 校验时,服务端重新生成签名,若不同说明数据被篡改。

完整 Token 示例:

1
2
3
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoidG9tIiwiZXhwIjoxNjk1MjAwNDAwfQ
.PxF8u9K5K77ZsYFshjGxgAn8FZc4T2oc-5Znbv7dJpo

二、JWT 的工作流程

  1. 用户登录

    • 用户提交账号密码 → 服务端验证 → 生成 JWT 返回给客户端。
  2. 客户端保存 Token

    • 一般存放在浏览器 localStorage / sessionStorage / 移动端缓存 / Cookie。
  3. 后续请求

    • 客户端在请求头里加上:

      1
      Authorization: Bearer <JWT_TOKEN>
  4. 服务端校验 Token

    • 解码 + 校验签名 + 校验过期时间 → 通过则放行,否则拒绝。

四、Go 操作 JWT 的讲解

安装依赖:

1
go get github.com/golang-jwt/jwt/v5

1. 定义秘钥和声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"

"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte("mySecretKey") // 秘钥

// 自定义声明
type MyClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
  1. 生成 Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 生成 JWT Token
func GenerateToken(userID int, username string) (string, error) {
claims := MyClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), // 过期时间
IssuedAt: jwt.NewNumericDate(time.Now()), // 签发时间
Issuer: "myApp", // 签发人
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}

3. 解析 & 校验 Token

1
2
3
4
5
6
7
8
9
10
// 解析 JWT Token
func ParseToken(tokenStr string) (*MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, nil
}
return nil, err
}

五、完整 Demo 小项目(基于 Gin + JWT)

下面是一个简单的 用户登录 + 鉴权接口 小项目:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package main

import (
"net/http"
"time"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte("mySecretKey")

type MyClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}

func GenerateToken(userID int, username string) (string, error) {
claims := MyClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "gin-jwt-demo",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}

func ParseToken(tokenStr string) (*MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, nil
}
return nil, err
}

// JWT 中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenStr := c.GetHeader("Authorization")
if tokenStr == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少 Token"})
c.Abort()
return
}

claims, err := ParseToken(tokenStr[7:]) // 去掉 "Bearer "
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效 Token"})
c.Abort()
return
}

// 把用户信息放到上下文
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)

c.Next()
}
}

func main() {
r := gin.Default()

// 登录接口
r.POST("/login", func(c *gin.Context) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}

// 模拟用户验证
if req.Username == "tom" && req.Password == "123456" {
token, _ := GenerateToken(1, req.Username)
c.JSON(http.StatusOK, gin.H{"token": token})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "账号或密码错误"})
}
})

// 受保护接口
r.GET("/profile", AuthMiddleware(), func(c *gin.Context) {
username := c.GetString("username")
userID := c.GetInt("user_id")
c.JSON(http.StatusOK, gin.H{
"user_id": userID,
"username": username,
"msg": "这是一个受保护的接口",
})
})

r.Run(":8080")
}

测试流程:

  1. POST /login

    1
    {"username": "tom", "password": "123456"}

    返回一个 Token。

  2. GET /profile
    请求头加:

    1
    Authorization: Bearer <token>

    返回:

    1
    {"user_id":1,"username":"tom","msg":"这是一个受保护的接口"}

一.JWT常用API


一、核心结构 & 概念

  1. Token 结构
1
2
3
4
5
6
7
8
type Token struct {
Raw string // 原始 token 字符串
Method SigningMethod // 签名方法
Header map[string]interface{} // Header
Claims Claims // Payload (声明)
Signature string // 签名
Valid bool // 是否有效
}
  1. Claims(声明)
  • 标准声明(RegisteredClaims)
    • exp:过期时间(Expiration Time)
    • iat:签发时间(Issued At)
    • nbf:生效时间(Not Before)
    • iss:签发人(Issuer)
    • sub:主题(Subject)
    • aud:接收方(Audience)
    • jti:JWT ID(唯一标识)
  • 自定义声明
    可以扩展字段,比如 UserIDRole
1
2
3
4
5
type MyClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}

二、常用 API

1. 创建 Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 方式 1:使用标准声明
claims := jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), // 过期时间
Issuer: "myApp",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// 方式 2:使用自定义声明
myClaims := MyClaims{
UserID: 1,
Username: "tom",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)

2. 签发 Token

1
tokenString, err := token.SignedString([]byte("mySecretKey"))

3. 解析 & 验证 Token

1
2
3
4
5
6
7
8
9
10
11
parsedToken, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("mySecretKey"), nil
})

// 校验
if claims, ok := parsedToken.Claims.(*MyClaims); ok && parsedToken.Valid {
fmt.Println("UserID:", claims.UserID)
fmt.Println("Username:", claims.Username)
} else {
fmt.Println("Token 无效:", err)
}

4. 解码 Header 和 Payload(无校验)

1
2
3
4
5
6
7
// 仅解码,不验证签名
parts := strings.Split(tokenString, ".")
headerJSON, _ := base64.RawURLEncoding.DecodeString(parts[0])
payloadJSON, _ := base64.RawURLEncoding.DecodeString(parts[1])

fmt.Println("Header:", string(headerJSON))
fmt.Println("Payload:", string(payloadJSON))

5. 预置的签名算法

  • jwt.SigningMethodHS256
  • jwt.SigningMethodHS384
  • jwt.SigningMethodHS512
  • jwt.SigningMethodRS256(需要 RSA 私钥/公钥)
  • jwt.SigningMethodES256(需要 ECDSA 密钥)

三、常见错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
parsedToken, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("mySecretKey"), nil
})

if err != nil {
if errors.Is(err, jwt.ErrTokenMalformed) {
fmt.Println("非法的 Token")
} else if errors.Is(err, jwt.ErrTokenExpired) {
fmt.Println("Token 已过期")
} else if errors.Is(err, jwt.ErrTokenNotValidYet) {
fmt.Println("Token 还未生效")
} else {
fmt.Println("无法处理的 Token:", err)
}
}

四、常用 API 总结(速查表)

功能 API/方法
创建 Token jwt.NewWithClaims(method, claims)
签发 Token token.SignedString(secret)
解析 & 校验 Token jwt.ParseWithClaims(tokenStr, claims, keyFunc)
自定义声明 自定义 struct + jwt.RegisteredClaims
过期时间 ExpiresAt: jwt.NewNumericDate(time.Now().Add(x))
签名算法 jwt.SigningMethodHS256 / RS256 / ES256
错误处理 jwt.ErrTokenExpired / jwt.ErrTokenMalformed
[up主专用,视频内嵌代码贴在这]