71 lines
1.8 KiB
Go
71 lines
1.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/hhs/camtalk/internal/trace"
|
|
)
|
|
|
|
// contextKey 用于在 Gin context 中存储 Claims 的 key。
|
|
const (
|
|
ContextKeyUserID = "user_id"
|
|
ContextKeyUsername = "username"
|
|
)
|
|
|
|
// AuthMiddleware 返回 Gin 中间件,从 Authorization: Bearer <token> 提取并校验 JWT。
|
|
// 校验成功后将 user_id 和 username 写入 Gin Context。
|
|
func AuthMiddleware(tokenMgr *TokenManager) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
log := trace.FromContext(c.Request.Context())
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
log.Warnw("auth rejected",
|
|
"client_ip", c.ClientIP(),
|
|
"path", c.Request.URL.Path,
|
|
"reason", "missing authorization header")
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"code": "INVALID_TOKEN",
|
|
"message": "missing authorization header",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 提取 Bearer token
|
|
parts := strings.SplitN(authHeader, " ", 2)
|
|
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
|
|
log.Warnw("auth rejected",
|
|
"client_ip", c.ClientIP(),
|
|
"path", c.Request.URL.Path,
|
|
"reason", "invalid authorization format")
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"code": "INVALID_TOKEN",
|
|
"message": "invalid authorization format",
|
|
})
|
|
return
|
|
}
|
|
|
|
claims, err := tokenMgr.ValidateAccess(parts[1])
|
|
if err != nil {
|
|
log.Warnw("auth rejected",
|
|
"client_ip", c.ClientIP(),
|
|
"path", c.Request.URL.Path,
|
|
"reason", "invalid or expired token",
|
|
"error", err)
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"code": "INVALID_TOKEN",
|
|
"message": "invalid or expired token",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 将用户信息写入 context
|
|
c.Set(ContextKeyUserID, claims.UserID)
|
|
c.Set(ContextKeyUsername, claims.Username)
|
|
|
|
c.Next()
|
|
}
|
|
}
|