共计 2492 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在分布式系统中,API Token 的管理一直是个头疼的问题。直接传递原始 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):
- 用户发起敏感操作请求。
- 中转站返回 2FA 验证请求。
- 用户输入验证码完成验证。
- 中转站生成临时 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: trueHeader,下游服务可据此识别并拒绝代理请求。
开放性问题
如何在中转层实现细粒度的权限熔断?
欢迎在评论区分享你的想法和实践经验。
正文完
