Step 06a — 06g | 從記憶體到持久化,從匿名到認證
| 層 | 知道什麼 | 不知道什麼 | Java 對應 |
|---|---|---|---|
| handler | HTTP、gin.Context | 密碼怎麼加密、SQL | @RestController |
| service | bcrypt、JWT、業務規則 | HTTP 狀態碼 | @Service |
| repository | GORM、SQL | 為什麼要查這筆 | @Repository |
不是每個功能都需要 service 層。如果 handler 和 repository 之間沒有邏輯(GetMessages),直接呼叫 repository。需要時才抽象。
// 加密(每次產生不同 hash,自動加鹽)
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
// 比對(從 hash 裡取出 salt 再比)
bcrypt.CompareHashAndPassword(hash, []byte(inputPassword))
不用自己管理 salt — salt 嵌在 hash 字串裡,重啟 server、換機器都能比對。
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.xxxxx
header payload signature
payload 裡放 user_id、username、exp(過期時間)。用 HS256 對稱加密簽名。
| HS256(我們用的) | ES256 | |
|---|---|---|
| 加密方式 | 對稱(一個 secret) | 非對稱(公私鑰) |
| 適用場景 | 單一 server | 微服務(各服務只需公鑰驗證) |
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 驗證...
c.Set("username", claims["username"]) // 存值給下游
c.Next() // 放行
// 或
c.Abort() // 攔截
}
}
// 公開
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()。
| Java (JPA / Hibernate) | Go (GORM) |
|---|---|
@Entity | type User struct + struct tag |
@Column(unique = true) | gorm:"uniqueIndex" |
@JsonIgnore | json:"-" |
repository.save(user) | DB.Create(&user) |
findByUsername(x) | DB.Where("username = ?", x).First(&user) |
ddl-auto=update | DB.AutoMigrate(&User{}) |
瀏覽器的 new WebSocket() 不支援自定義 header。解法:query parameter 傳 token。
ws = new WebSocket(`ws://host/ws?token=${token}`);
middleware 先查 header,沒有再查 query parameter。