API Token 中转站架构设计与实现:从零搭建高安全性的认证代理服务

1次阅读
没有评论

共计 2492 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

背景痛点

在分布式系统中,API Token 的管理一直是个头疼的问题。直接传递原始 Token 会带来诸多安全隐患:

API Token 中转站架构设计与实现:从零搭建高安全性的认证代理服务

  • MITM 攻击风险 :Token 在传输过程中可能被截获,攻击者可以冒充合法用户发起请求。
  • 权限扩散问题 :一旦 Token 泄露,攻击者可以访问所有授权资源,无法进行细粒度控制。
  • 无法有效审计 :原始 Token 难以追踪使用情况,无法进行有效的访问日志记录。

为了解决这些问题,我们需要一个集中式的 Token 中转站,作为微服务间的认证代理。

技术选型

JWT vs Opaque Token

  • JWT:自包含的 Token,包含声明信息,适合用于分布式系统中的身份认证。
  • Opaque Token:不透明的 Token,需要额外查询才能获取用户信息,增加了系统复杂性。

我们选择 JWT + Redis 的组合,原因如下:

  • TTL 控制 :通过 Redis 可以灵活设置 Token 的过期时间,避免长期有效的 Token 带来的安全风险。
  • 黑名单机制 :利用 Redis 的高性能,可以快速将失效的 Token 加入黑名单,防止被滥用。

核心实现

Token 验签 / 转换

以下是 Golang 的核心代码示例:

func validateAndConvertToken(originalToken string) (string, error) {
    // 验证原始 Token 的签名
    token, err := jwt.Parse(originalToken, func(token *jwt.Token) (interface{}, error) {return publicKey, nil})
    if err != nil {return "", fmt.Errorf("invalid token: %v", err)
    }

    // 生成新的 JWT Token
    newToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{"sub": token.Claims.(jwt.MapClaims)["sub"],
        "exp": time.Now().Add(time.Hour * 1).Unix(),})

    signedToken, err := newToken.SignedString(privateKey)
    if err != nil {return "", fmt.Errorf("failed to sign token: %v", err)
    }

    return signedToken, nil
}

请求代理

func proxyRequest(w http.ResponseWriter, r *http.Request) {
    // 验证并转换 Token
    newToken, err := validateAndConvertToken(r.Header.Get("Authorization"))
    if err != nil {http.Error(w, err.Error(), http.StatusUnauthorized)
        return
    }

    // 创建新的请求
    proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
    if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 设置新的 Token
    proxyReq.Header.Set("Authorization", "Bearer"+newToken)

    // 发送请求
    client := &http.Client{}
    resp, err := client.Do(proxyReq)
    if err != nil {http.Error(w, err.Error(), http.StatusBadGateway)
        return
    }
    defer resp.Body.Close()

    // 返回响应
    w.WriteHeader(resp.StatusCode)
    io.Copy(w, resp.Body)
}

限流中间件

func rateLimitMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 获取客户端 IP
        ip := strings.Split(r.RemoteAddr, ":")[0]

        // 检查限流
        if !limiter.Allow(ip) {http.Error(w, "too many requests", http.StatusTooManyRequests)
            return
        }

        next.ServeHTTP(w, r)
    })
}

安全设计

防范 Replay Attack

  • Nonce 机制 :每次请求生成唯一的 Nonce,并在 Redis 中记录,防止重复使用。
  • 时间戳校验 :检查 Token 中的时间戳,拒绝过期的请求。

双因素认证集成

对于敏感操作,可以集成双因素认证(2FA):

  1. 用户发起敏感操作请求。
  2. 中转站返回 2FA 验证请求。
  3. 用户输入验证码完成验证。
  4. 中转站生成临时 Token,有效期仅限当前操作。

性能优化

Redis 连接池配置

func initRedisPool() *redis.Pool {
    return &redis.Pool{
        MaxIdle:     10,
        MaxActive:   100,
        IdleTimeout: 240 * time.Second,
        Dial: func() (redis.Conn, error) {return redis.Dial("tcp", "localhost:6379")
        },
    }
}

压测数据

我们进行了性能压测,结果如下:

场景 QPS 平均延迟 (ms)
无中转站 12000 8.2
有中转站 9800 10.5

虽然中转站带来了一定的性能开销,但在安全性和可管理性上的提升是值得的。

避坑指南

时钟漂移问题

  • 使用 NTP 同步服务器时间。
  • 在 Token 验证时允许一定的时间偏差(如 ±30 秒)。

循环代理问题

  • 严格规范 Header 处理,避免无限循环。
  • 在中转站添加 X-Proxy: true Header,下游服务可据此识别并拒绝代理请求。

开放性问题

如何在中转层实现细粒度的权限熔断?

欢迎在评论区分享你的想法和实践经验。

正文完
 0
评论(没有评论)