Skip to content

Gin, JWT, API Design

Phase 03 — Go Web 涵盖:Gin · JWT · API design


1. 学习目标

完成本知识点后,你应该能够:

  • 了解 Gin 框架的基本用法,并理解其与 net/http 的关系
  • 说明何时用标准库、何时引入 Gin
  • 理解 JWT 的结构(Header.Payload.Signature)和无状态认证流程
  • 在 Go 侧签发和验证 JWT
  • 应用 API 设计最佳实践(版本、命名、错误码、分页)
  • 规划 Project 01 — Go REST API 的整体结构

2. 为什么需要

net/http 能完成所有工作,但路由分组、JSON binding、中间件链在大型项目中会显得繁琐。Gin 是 Go 最流行的 Web 框架之一,语法类似 Express,适合快速开发——但路线要求你先理解标准库,Gin 作为效率工具引入。

JWT 是前后端分离项目的常见认证方案。Vue 前端登录后存 token,后续请求带 Authorization: Bearer <token>;Go 后端验证签名和过期时间。你前端可能已用过 JWT,本节点重点在 Go 侧实现

规范的 API design 让 Vue + Three.js 团队协作有明确契约:URL 怎么命名、错误怎么返回、分页怎么传参——减少联调时的口头对齐成本。


3. 核心概念

3.1 Gin 简介

go
r := gin.Default()
r.GET("/api/devices", listDevices)
r.POST("/api/devices", createDevice)
r.Run(":8080")
特性Ginnet/http
路由r.GET, GroupServeMux
JSONc.JSON(200, obj)手动 Encode
参数c.Param("id"), c.Query("page")PathValue, URL.Query
Bindingc.ShouldBindJSON(&req)json.Decoder
中间件r.Use(mw)手动 wrap

建议:练习项目先用 net/http;Project 01 可选用 Gin 提高效率。

3.2 JWT 结构

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature
  ─── Header ───   ───── Payload ─────   ── Sig ──
部分内容
Header算法(如 HS256)
Payloadclaims(sub, exp, iat 等)
SignatureHMAC(header.payload, secret)

Payload 是 Base64 编码,不是加密——任何人可解码,不要放密码或密钥。

3.3 认证流程

1. POST /api/login { username, password }
2. 服务端验证 → 返回 { token: "eyJ..." }
3. 前端存 localStorage / cookie
4. 后续请求 Header: Authorization: Bearer eyJ...
5. 中间件验证 token → 注入 user 到 context

3.4 API Design 要点

  • 版本前缀:/api/v1/
  • 名词复数资源:/devices
  • 统一响应 envelope
  • 错误码与 HTTP status 对齐
  • 分页、过滤、排序约定
  • OpenAPI 文档(可选)

4. 基础语法

Gin 示例

workspace/phase-03/gin-jwt-api-design/gin_example.go(可选运行)。

go
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

type Device struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

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

    api := r.Group("/api/v1")
    {
        api.GET("/devices", func(c *gin.Context) {
            c.JSON(http.StatusOK, []Device{
                {ID: "AGV-001", Name: "Alpha"},
            })
        })
        api.GET("/devices/:id", func(c *gin.Context) {
            id := c.Param("id")
            c.JSON(http.StatusOK, Device{ID: id, Name: "Demo"})
        })
    }

    r.Run(":8080")
}

JWT 示例(golang-jwt)

go
import (
    "time"
    jwt "github.com/golang-jwt/jwt/v5"
)

var secret = []byte("dev-secret-change-in-production")

func signToken(userID string) (string, error) {
    claims := jwt.MapClaims{
        "sub": userID,
        "exp": time.Now().Add(24 * time.Hour).Unix(),
        "iat": time.Now().Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(secret)
}

func verifyToken(tokenStr string) (string, error) {
    token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (any, error) {
        return secret, nil
    })
    if err != nil || !token.Valid {
        return "", err
    }
    claims := token.Claims.(jwt.MapClaims)
    return claims["sub"].(string), nil
}

Auth 中间件(Gin)

go
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        auth := c.GetHeader("Authorization")
        if len(auth) < 8 || auth[:7] != "Bearer " {
            c.AbortWithStatusJSON(401, gin.H{"message": "unauthorized"})
            return
        }
        userID, err := verifyToken(auth[7:])
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"message": "invalid token"})
            return
        }
        c.Set("userID", userID)
        c.Next()
    }
}

5. 代码解析

go
api := r.Group("/api/v1")

路由分组:共享前缀和中间件(如 auth)。类似 Express Router()

go
c.ShouldBindJSON(&req)

Gin 自动解析 JSON 到 struct,失败返回 400。底层仍是 json + reflection。

go
jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

HS256 对称加密:签发和验证用同一 secret。生产环境 secret 放环境变量;多服务场景可考虑 RSA。

go
"exp": time.Now().Add(24 * time.Hour).Unix()

过期时间必须校验,否则 token 永久有效。

go
c.AbortWithStatusJSON(401, ...)

认证失败时 Abort 阻止后续 handler 执行。


6. JavaScript / TypeScript 对比

概念Go GinVue / Express
框架Gin(可选)Express / 无(axios 直调)
JWT 库golang-jwtjsonwebtoken
存 token不涉及(服务端签发)localStorage / cookie
路由gin.RouterGroupvue-router(前端)
拦截器auth middlewareaxios interceptors

Vue axios 示例

typescript
// 请求拦截器
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

// 登录
const { data } = await axios.post('/api/v1/login', { username, password })
localStorage.setItem('token', data.token)

关键差异

  1. Go 负责 sign 和 verify;前端只存和发送
  2. JWT payload 可解码,只放 user id、role 等非敏感标识
  3. Gin 是第三方依赖;net/http 是标准库

7. 常见错误

错误 1:过早引入 Gin

未理解 net/http 就直接用 Gin,遇到问题时不知底层行为。

错误 2:JWT secret 硬编码

go
var secret = []byte("123456")  // ❌ 提交到 git
// ✅ os.Getenv("JWT_SECRET")

错误 3:不验证 exp

只 parse 不检查 token.Valid 和 exp claim。

错误 4:在 JWT 存敏感信息

password、apiKey 绝不能进 payload。

错误 5:API 无版本

后续 breaking change 无法平滑迁移。用 /api/v1/ 前缀。

错误 6:401 与 403 混用

  • 401 Unauthorized:未登录 / token 无效
  • 403 Forbidden:已认证但无权限

8. 实际应用

Project 01 结构建议

project-01/
├── cmd/api/main.go
├── internal/
│   ├── handler/      # HTTP handlers(net/http 或 Gin)
│   ├── middleware/   # auth, cors, logging
│   ├── model/        # Device, Scene struct
│   ├── service/      # 业务逻辑
│   └── store/        # 内存 / 后续 PostgreSQL
└── api/API.md        # 接口文档

数字孪生 API 认证

  • 管理端(Vue admin):JWT 登录后 CRUD 设备
  • 大屏只读:可选 API Key 或公开 read-only 端点
  • WebSocket(Phase 06):连接时 query token 或首条 auth 消息

统一响应 Envelope

go
type Envelope struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    any    `json:"data,omitempty"`
}

Vue 响应拦截器统一处理 code !== 0 的情况。

REST 端点总览(Project 01)

MethodPath认证说明
POST/api/v1/login登录获 token
GET/api/v1/profile当前用户
GET/api/v1/devices设备列表
POST/api/v1/devices创建设备
GET/api/v1/devices/:id设备详情

9. 深入理解

9.1 Gin vs 标准库选择

场景推荐
学习、小 demonet/http
Project 01、快速迭代Gin 或 chi
极简服务标准库

Gin 底层仍是 net/httpListenAndServe 接收连接后交给 Gin 引擎路由。

9.2 JWT vs Session

JWTSession (Redis)
状态无状态服务端存 session
撤销难(需黑名单)易 delete session
扩展多实例无需共享需 Redis 共享

初学 JWT 足够;Phase 05 Redis 后可对比 Session 方案。

9.3 Refresh Token

Access token 短过期(15min)+ refresh token 长过期(7d),降低 token 泄露风险。Project 01 可先做单 token,后续扩展。

9.4 API 文档

手写 API.md 或使用 Swagger(swaggo)。与 Vue 团队协作时文档是契约——字段名、状态码、错误格式必须一致。


10. 练习

详细题目见 exercises/phase-03-web/04-gin-jwt-api-design.md

Level 1 — 基础

练习 1.1:用 Gin 实现 GET /api/v1/health

练习 1.2:signToken 和 verifyToken roundtrip 测试。

练习 1.3:编写 API.md 描述 3 个 Device 端点。

Level 2 — 应用

练习 2.1:Gin auth middleware:无 token 返回 401。

练习 2.2:POST /login 返回 JWT,GET /profile 需 token。

练习 2.3:统一 Envelope 响应 helper。

Level 3 — 综合

练习 3.1:net/http 版 auth middleware + JWT,对比 Gin 版。

练习 3.2:完整 API 设计文档:devices CRUD + auth + 错误码表。

Level 4 — 项目实践

练习 4.1:在 workspace/phase-03/gin-jwt-api-design/ 启动 Project 01 骨架:登录 + 受保护的 Device API(内存 store,Gin 或 net/http 二选一)。


11. 学习检查

完成练习后,确认你能回答:

  1. Gin 与 net/http 是什么关系?
  2. JWT 三部分分别是什么?哪部分不能放敏感数据?
  3. 为什么生产环境 JWT secret 必须从环境变量读取?
  4. API 为什么要加 /api/v1 版本前缀?
  5. 401 和 403 分别表示什么?
  6. 何时选 Gin,何时坚持 net/http?
  7. Vue axios 拦截器如何配合 Go JWT 中间件?

12. 下一步

已完成下一知识点关系
Gin, JWT, API designPhase 04 DatabaseAPI 接入 PostgreSQL
Project 01完整 REST API 项目

Phase 03 完成!开始 Project 01,并准备 PostgreSQL 持久化。


依赖说明

Gin 和 golang-jwt 为第三方库。练习中若使用:

bash
go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5

学习阶段优先理解标准库实现;框架库作为加速工具。


学习导航

上一篇:Middleware、Validation、CORS · 对应练习 · 下一篇:SQL 基础与表设计