Day 9:用户认证与 JWT 实战
Table of Contents
Day 9:用户认证与 JWT 实战
在上一章中,我们用 Gin/Fiber 写了一个最基本的用户注册和登录接口。但当用户登录之后,我们需要在随后的 API 请求中知道 “这个用户是谁”,并验证用户是否有权限访问某些资源。这就是 用户认证 的核心。
本章我们将学习如何在 Go Web 服务中实现 JWT (JSON Web Token) 用户认证机制,并在任务管理系统中加上 登录态 和 鉴权。
1. 用户认证的常见方式
常见的 Web 服务认证方式有:
- Session + Cookie:后端保存 session,前端通过 cookie 传递 session ID(状态化,依赖存储)。
- Token(推荐):服务端生成 token,客户端每次请求时带上 token(无状态,适合微服务与移动端)。
- JWT(JSON Web Token):一种特别的 Token 格式,包含了用户信息和签名,无需存储。
我们选择 JWT,因为它:
- 无需服务端保存登录状态,适合微服务。
- 可以携带用户 ID、角色等信息。
- 有过期时间,安全可控。
2. JWT 结构解析
一个 JWT 长这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. # Header
eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IndhbHRlciIsImV4cCI6MTY5Nzg0NTYwMH0. # Payload
kOk0Ex1uT_z9mGidZ6ABR5m2ndTvn2kTQmYTxg5zFvI # Signature
三部分:
- Header:说明算法(如 HS256)
- Payload:用户数据(如
user_id=1
,exp=过期时间
) - Signature:签名(防篡改)
3. 项目需求
我们要实现:
- 用户注册(沿用 Day 8)
- 用户登录 -> 返回 JWT
- 用户访问任务接口时,必须带上 JWT
- JWT 过期后,需要重新登录
4. 实战:Go + Gin + JWT
我们用 Gin 和 github.com/golang-jwt/jwt/v5
库。
4.1 安装依赖
go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5
4.2 定义用户和内存存储
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
)
var userStore = map[string]string{} // 简单内存存储: username -> password
var jwtKey = []byte("my_secret_key") // JWT 密钥
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
4.3 用户注册 & 登录
// 注册
func register(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": "invalid request"})
return
}
if _, exists := userStore[req.Username]; exists {
c.JSON(http.StatusConflict, gin.H{"error": "user exists"})
return
}
userStore[req.Username] = req.Password
c.JSON(http.StatusOK, gin.H{"message": "register success"})
}
// 登录 -> 签发 JWT
func login(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": "invalid request"})
return
}
if userStore[req.Username] != req.Password {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
return
}
// 设置过期时间
expirationTime := time.Now().Add(1 * time.Hour)
claims := &Claims{
Username: req.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
// 生成 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not create token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": tokenString})
}
4.4 JWT 验证中间件
// 认证中间件
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
c.Abort()
return
}
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
c.Abort()
return
}
// 存储用户名到上下文
c.Set("username", claims.Username)
c.Next()
}
}
4.5 受保护的任务接口
func taskList(c *gin.Context) {
username := c.GetString("username")
c.JSON(http.StatusOK, gin.H{
"tasks": []string{
"Task1 for " + username,
"Task2 for " + username,
},
})
}
4.6 主程序
func main() {
r := gin.Default()
r.POST("/register", register)
r.POST("/login", login)
auth := r.Group("/api")
auth.Use(authMiddleware())
{
auth.GET("/tasks", taskList)
}
r.Run(":8080")
}
5. 测试流程
- 注册:
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"username":"walter","password":"123"}'
- 登录获取 JWT:
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username":"walter","password":"123"}'
返回:
{"token":"eyJhbGciOi..."}
- 访问任务接口:
curl -X GET http://localhost:8080/api/tasks \
-H "Authorization: eyJhbGciOi...JWT"
返回:
{"tasks":["Task1 for walter","Task2 for walter"]}
6. 小结
- 了解了 JWT 的结构与原理。
- 使用 Gin 实现了 注册、登录、签发 Token、鉴权中间件。
- 将任务接口保护起来,只有登录后才能访问。
7. 思考与练习
- 修改 Token 过期时间为 5 分钟,过期后如何刷新 Token?
- 如何在 JWT 中加入用户角色字段,并在中间件中进行权限校验?
- 将用户存储从内存改为 数据库(Day 10 将实现)。
Comments |0|
Category: 似水流年