go mod init my-gin-app?初始化一個 Go 項目,創建一個go.mod
文件
go mod tidy? ? ? ? ? ? ? ? ? ?自動整理項目依賴,確保go.mod
和go.sum
文件與代碼實際使用的依賴一致
go mod init
:創建項目的 “依賴說明書”。go mod tidy
:整理 “說明書”,讓依賴清單精確匹配代碼。
代碼基礎
package mainimport "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
}
參數查詢代碼
package mainimport "github.com/gin-gonic/gin"func main() {// 創建默認引擎,包含日志和恢復中間件r := gin.Default()// 處理帶參數的 URL:/users/123r.GET("/users/:id", func(c *gin.Context) {id := c.Param("id")c.JSON(200, gin.H{"user_id": id,})})// 啟動服務器,監聽 8080 端口r.Run() // 默認監聽 :8080
}
處理 POST 請求并驗證賬號密碼
package mainimport "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
package mainimport ("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 LoginRequestif 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測試
curl -X POST http://localhost:8080/login \-H "Content-Type: application/json" \-d '{"username": "admin", "password": "password"}'
響應
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjk5OTk5OTk5LCJpYXQiOjE2OTk5OTk2OTksImlzcyI6InlvdXItYXBwIn0.abcdefghijklmnopqrstuvwxyz123456"
}
然后
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)。
// 定義Claims結構體用于存儲解析結果
claims := &Claims{}// 用密鑰"解鎖"Token(驗證簽名并解析內容)
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {return jwtKey, nil // 返回密鑰,用于驗證簽名
})
以下代碼模擬了一個被篡改的 Token 的驗證過程:
package mainimport ("fmt""github.com/dgrijalva/jwt-go"
)var jwtKey = []byte("your-secret-key")func main() {// 1. 生成一個合法的Tokenclaims := 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. 驗證篡改后的TokentamperedToken, tamperedErr := jwt.ParseWithClaims(tamperedTokenStr, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {return jwtKey, nil})fmt.Println("篡改Token驗證結果:", tamperedToken.Valid, tamperedErr)
}
輸出結果:
原始Token驗證結果: true <nil>
篡改Token驗證結果: false signature is invalid
從 Token 中獲取 username 的核心代碼
1. 定義 Claims 結構體
首先需要定義一個結構體,用于存儲 Token 中的數據:
type Claims struct {Username string `json:"username"` // 對應Token中的username字段jwt.StandardClaims // 包含標準字段(如過期時間、簽發者等)
}
2. 驗證并解析 Token
// 驗證Token并獲取Claims
claims, err := validateToken(tokenStr)
if err != nil {// 處理驗證失敗的情況log.Fatal("Token驗證失敗:", err)
}// 從Claims中獲取username
username := claims.Username
fmt.Println("用戶名:", username)
3. 完整的驗證函數示例
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
}