如何在Gin中结合JWT实现基于路由授权的静态文件服务?
实现基于JWT的用户专属视频目录权限控制
核心思路是放弃直接使用r.Static,因为它无法嵌入权限校验逻辑。我们需要自定义请求处理流程:先通过JWT验证身份,校验请求路径与用户身份的匹配性,最后安全返回对应视频文件。以下是具体实现步骤(以Gin框架为例):
1. 编写JWT验证中间件
把JWT解析逻辑抽成中间件,统一处理身份验证,并将用户名存入请求上下文:
import ( "fmt" "os" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) func JWTAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 从请求头获取Bearer Token authHeader := c.GetHeader("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { c.AbortWithStatusJSON(401, gin.H{"error": "未提供有效身份凭证"}) return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") // 解析Token并提取用户名 token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { // 校验签名算法 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("非法签名算法: %v", token.Header["alg"]) } // 返回JWT密钥(建议用环境变量存储,不要硬编码) return []byte(os.Getenv("JWT_SECRET")), nil }) if err != nil || !token.Valid { c.AbortWithStatusJSON(401, gin.H{"error": "身份凭证无效"}) return } // 从Token Claims中提取用户名 claims, ok := token.Claims.(jwt.MapClaims) if !ok { c.AbortWithStatusJSON(401, gin.H{"error": "身份信息解析失败"}) return } username, ok := claims["username"].(string) if !ok { c.AbortWithStatusJSON(401, gin.H{"error": "用户信息缺失"}) return } // 将用户名存入上下文,供后续处理使用 c.Set("username", username) c.Next() } }
2. 编写视频文件处理Handler
在Handler中完成权限校验、路径安全校验,并返回视频文件:
import ( "path/filepath" "os" "strings" "github.com/gin-gonic/gin" ) func VideoHandler(c *gin.Context) { // 从上下文获取当前登录用户 username, exists := c.Get("username") if !exists { c.AbortWithStatusJSON(401, gin.H{"error": "未授权访问"}) return } currentUser := username.(string) // 解析请求路径中的用户目录部分 reqPath := c.Request.URL.Path pathParts := strings.Split(strings.TrimPrefix(reqPath, "/videos/"), "/") if len(pathParts) == 0 { c.AbortWithStatusJSON(400, gin.H{"error": "无效的请求路径"}) return } reqUserDir := pathParts[0] // 校验请求的目录是否属于当前用户 if reqUserDir != currentUser { c.AbortWithStatusJSON(403, gin.H{"error": "无权限访问该目录"}) return } // 安全构造实际文件路径(防止路径遍历攻击) relativeFilepath := strings.Join(pathParts[1:], "/") targetFilePath := filepath.Join("./videos", currentUser, relativeFilepath) // 验证文件路径是否在用户专属目录内(避免../等遍历手段) userRootDir, _ := filepath.Abs(filepath.Join("./videos", currentUser)) absTargetPath, err := filepath.Abs(targetFilePath) if err != nil || !strings.HasPrefix(absTargetPath, userRootDir) { c.AbortWithStatusJSON(403, gin.H{"error": "非法访问请求"}) return } // 检查文件是否存在 if _, err := os.Stat(absTargetPath); os.IsNotExist(err) { c.AbortWithStatusJSON(404, gin.H{"error": "文件不存在"}) return } // 返回视频文件 c.File(absTargetPath) }
3. 注册路由
替换原有的r.Static,使用自定义的中间件和Handler:
func main() { r := gin.Default() // 注册视频服务路由,加入JWT验证中间件 r.GET("/videos/*filepath", JWTAuthMiddleware(), VideoHandler) r.Run(":8080") }
关键注意事项
- 路径安全:必须用
filepath.Join和绝对路径校验防止路径遍历攻击,避免用户通过/videos/testuser/../user1/video.mp4这类路径越权访问。 - JWT密钥管理:不要硬编码密钥,通过环境变量配置,降低泄露风险。
- 错误反馈:返回明确的HTTP状态码和错误信息,方便前端处理。
内容的提问来源于stack exchange,提问作者Westsi
相关产品推荐
相关产品推荐

