go mod init my-gin-app 初始化一个 Go 项目,创建一个go.mod
文件
go mod tidy 自动整理项目依赖,确保go.mod
和go.sum
文件与代码实际使用的依赖一致
go mod init
:创建项目的 "依赖说明书"。go mod tidy
:整理 "说明书",让依赖清单精确匹配代码。
代码基础
Go
package main
import "github.com/gin-gonic/gin"
func main() {
// 创建默认引擎,包含日志和恢复中间件
r := gin.Default()
// 定义根路径的 GET 请求
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello World",
})
})
// 启动服务器,监听 8080 端口
r.Run() // 默认监听 :8080
}
参数查询代码
Go
package main
import "github.com/gin-gonic/gin"
func main() {
// 创建默认引擎,包含日志和恢复中间件
r := gin.Default()
// 处理带参数的 URL:/users/123
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{
"user_id": id,
})
})
// 启动服务器,监听 8080 端口
r.Run() // 默认监听 :8080
}
处理 POST 请求并验证账号密码
Go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
// 处理 POST 请求,验证账号密码
r.POST("/login", func(c *gin.Context) {
// 定义请求体结构
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
var req LoginRequest
// 绑定 JSON 请求体并验证
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 验证账号密码
if req.Username == "123" && req.Password == "123" {
c.JSON(200, gin.H{"message": "验证正确"})
} else {
c.JSON(401, gin.H{"message": "账号或密码错误"})
}
})
r.Run()
}
json:"password"
就是这个「翻译器」。- 当你收到一个 JSON 数据(比如
{"password": "123"}
),Gin 会自动把 JSON 里的password
字段,对应到 Go 代码里的Password
变量。
binding:"required"
表示这个字段「必须存在」,如果 JSON 里没有这个字段,就会报错。
ShouldBindJSON
会做两件事
- 翻译 :把 JSON 里的字段名(比如
password
),对应到 Go 结构体的变量(比如Password
)。 - 检查规则 :检查每个字段是否符合
binding:"required"
等规则。
JWT
Go
package main
import (
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// 密钥(生产环境应从配置文件或环境变量获取)
var jwtKey = []byte("your-secret-key")
// Token中包含的用户信息
type Claims struct {
Username string `json:"username"`
jwt.StandardClaims
}
// 生成JWT Token
func generateToken(username string) (string, error) {
expirationTime := time.Now().Add(1 * time.Hour)
claims := &Claims{
Username: username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
IssuedAt: time.Now().Unix(),
Issuer: "your-app",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
// 验证JWT Token
func validateToken(tokenStr string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil || !token.Valid {
return nil, err
}
return claims, nil
}
// 登录接口 - 验证用户并生成Token
func loginHandler(c *gin.Context) {
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Username != "admin" || req.Password != "password" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的凭证"})
return
}
token, err := generateToken(req.Username)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成Token失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
}
// 认证中间件 - 检查请求中的Token
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenStr := c.GetHeader("Authorization")
if tokenStr == "" || len(tokenStr) < 7 || tokenStr[:7] != "Bearer " {
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少或格式错误的Token"})
c.Abort()
return
}
tokenStr = tokenStr[7:]
claims, err := validateToken(tokenStr)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的Token"})
c.Abort()
return
}
c.Set("username", claims.Username)
c.Next()
}
}
// 受保护的接口 - 需要有效的Token才能访问
func protectedHandler(c *gin.Context) {
username := c.MustGet("username").(string)
c.JSON(http.StatusOK, gin.H{
"message": "欢迎回来," + username,
"data": "这是受保护的数据",
})
}
func main() {
r := gin.Default()
r.POST("/login", loginHandler)
authGroup := r.Group("/api")
authGroup.Use(authMiddleware())
{
authGroup.GET("/protected", protectedHandler)
}
r.Run()
}
Apipost测试
Go
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password"}'
响应
Go
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjk5OTk5OTk5LCJpYXQiOjE2OTk5OTk2OTksImlzcyI6InlvdXItYXBwIn0.abcdefghijklmnopqrstuvwxyz123456"
}
然后
Go
curl -X GET http://localhost:8080/api/protected \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
var jwtKey = []byte("your-secret-key") your-secret-key
可以随便写吗?
❶ 开发测试时:可以写简单的,但要注意
❷ 正式环境:必须用复杂、安全的密码
-
- 比如用
your-secret-key
或123456
,方便测试代码是否正常运行。 - 但千万不要在正式环境用这种简单密码!就像日记本用 "123456" 当密码,很容易被别人破解。
- 规则 :
- 长度至少 16 位,包含字母、数字、特殊符号(比如
!@#$%^&*()
)。 - 不能是任何人都能猜到的内容(比如生日、名字)。
- 长度至少 16 位,包含字母、数字、特殊符号(比如
- 比如用
在 JWT 验证流程中,用密钥 "解锁" Token 的核心代码是 jwt.ParseWithClaims
函数。这个函数会验证 Token 的签名,并解析出其中的内容(Claims)。
Go
// 定义Claims结构体用于存储解析结果
claims := &Claims{}
// 用密钥"解锁"Token(验证签名并解析内容)
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil // 返回密钥,用于验证签名
})
以下代码模拟了一个被篡改的 Token 的验证过程:
Go
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
)
var jwtKey = []byte("your-secret-key")
func main() {
// 1. 生成一个合法的Token
claims := jwt.MapClaims{
"username": "alice",
"exp": jwt.TimeFunc().Add(time.Hour * 24).Unix(), // 24小时后过期
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, _ := token.SignedString(jwtKey)
fmt.Println("原始Token:", tokenStr)
// 2. 模拟篡改Token(修改Payload中的username)
// 注意:实际中无法直接修改,这里仅为演示验证失败的效果
tamperedTokenStr := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MTY5OTQwNjQwMH0.abc123" // 伪造的Token
// 3. 验证原始Token(正常情况)
validToken, validErr := jwt.ParseWithClaims(tokenStr, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
fmt.Println("原始Token验证结果:", validToken.Valid, validErr)
// 4. 验证篡改后的Token
tamperedToken, tamperedErr := jwt.ParseWithClaims(tamperedTokenStr, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
fmt.Println("篡改Token验证结果:", tamperedToken.Valid, tamperedErr)
}
输出结果:
Go
原始Token验证结果: true <nil>
篡改Token验证结果: false signature is invalid
从 Token 中获取 username 的核心代码
1. 定义 Claims 结构体
首先需要定义一个结构体,用于存储 Token 中的数据:
Go
type Claims struct {
Username string `json:"username"` // 对应Token中的username字段
jwt.StandardClaims // 包含标准字段(如过期时间、签发者等)
}
2. 验证并解析 Token
Go
// 验证Token并获取Claims
claims, err := validateToken(tokenStr)
if err != nil {
// 处理验证失败的情况
log.Fatal("Token验证失败:", err)
}
// 从Claims中获取username
username := claims.Username
fmt.Println("用户名:", username)
3. 完整的验证函数示例
Go
func validateToken(tokenStr string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
// 验证签名方法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("无效的签名方法: %v", token.Header["alg"])
}
// 返回密钥
return jwtKey, nil
})
// 检查验证错误
if err != nil {
return nil, err
}
// 检查Token有效性
if !token.Valid {
return nil, errors.New("无效的Token")
}
return claims, nil
}