You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 00:20:29