Phase 2: Auth + JWT + 資料庫

Step 06a — 06g | 從記憶體到持久化,從匿名到認證

三層架構

handler/auth.go ← HTTP 進出(解析 request、回 JSON) ↓ 呼叫 service/auth.go ← 業務邏輯(bcrypt 加密、JWT 產生) ↓ 呼叫 repository/user.go ← DB 操作(GORM CRUD)
知道什麼不知道什麼Java 對應
handlerHTTP、gin.Context密碼怎麼加密、SQL@RestController
servicebcrypt、JWT、業務規則HTTP 狀態碼@Service
repositoryGORM、SQL為什麼要查這筆@Repository

不是每個功能都需要 service 層。如果 handler 和 repository 之間沒有邏輯(GetMessages),直接呼叫 repository。需要時才抽象。

bcrypt 密碼加密

// 加密(每次產生不同 hash,自動加鹽)
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

// 比對(從 hash 裡取出 salt 再比)
bcrypt.CompareHashAndPassword(hash, []byte(inputPassword))

不用自己管理 salt — salt 嵌在 hash 字串裡,重啟 server、換機器都能比對。

JWT 認證流程

POST /api/register → 建立 user(bcrypt hash 密碼) POST /api/login → 比對密碼 → 產生 JWT token → 回傳給前端 GET /ws → JWT middleware 驗證 token → 放行 GET /api/messages → JWT middleware 驗證 token → 放行

JWT Token 結構

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.xxxxx
       header              payload            signature

payload 裡放 user_id、username、exp(過期時間)。用 HS256 對稱加密簽名。

HS256 vs ES256

HS256(我們用的)ES256
加密方式對稱(一個 secret)非對稱(公私鑰)
適用場景單一 server微服務(各服務只需公鑰驗證)

Gin Middleware

middleware 格式

func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 驗證...
        c.Set("username", claims["username"])  // 存值給下游
        c.Next()    // 放行
        // 或
        c.Abort()   // 攔截
    }
}

路由群組控制 middleware

// 公開
api := r.Group("/api")
api.POST("/register", handler.Register)

// 受保護
protected := r.Group("/")
protected.Use(middleware.JWTAuth())
protected.GET("/ws", ws.HandleWebSocket)

等於 Spring 的 antMatchers("/ws").authenticated()

GORM 對照

Java (JPA / Hibernate)Go (GORM)
@Entitytype User struct + struct tag
@Column(unique = true)gorm:"uniqueIndex"
@JsonIgnorejson:"-"
repository.save(user)DB.Create(&user)
findByUsername(x)DB.Where("username = ?", x).First(&user)
ddl-auto=updateDB.AutoMigrate(&User{})

WebSocket 無法帶 Authorization Header

瀏覽器的 new WebSocket() 不支援自定義 header。解法:query parameter 傳 token。

ws = new WebSocket(`ws://host/ws?token=${token}`);

middleware 先查 header,沒有再查 query parameter。