Skip to content

Commit fa55b1c

Browse files
committed
feat: 实现代理链路真实 IP 处理
- 创建 utils/ip.go:GetRealIP 函数,正确处理 X-Forwarded-For / X-Real-IP - 更新所有使用 ClientIP 的地方:middleware/ratelimit.go, middleware/logger.go, handlers - 实现 redo.md 2.5:代理链路真实 IP 处理
1 parent 22b31e5 commit fa55b1c

File tree

6 files changed

+67
-5
lines changed

6 files changed

+67
-5
lines changed

internal/httpv2/handlers/auth_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (h *AuthHandler) UpdateToken(c *gin.Context) {
174174
Action: "token.rotate",
175175
ResourceType: "user",
176176
ResourceID: &userID,
177-
IP: c.ClientIP(),
177+
IP: utils.GetRealIP(c.Request),
178178
UserAgent: c.GetHeader("User-Agent"),
179179
Details: map[string]interface{}{
180180
"role": role,

internal/httpv2/handlers/link_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func (h *LinkHandler) DeleteLink(c *gin.Context) {
223223
Action: "link.delete",
224224
ResourceType: "link",
225225
ResourceID: &linkID,
226-
IP: c.ClientIP(),
226+
IP: utils.GetRealIP(c.Request),
227227
UserAgent: c.GetHeader("User-Agent"),
228228
Details: map[string]interface{}{
229229
"code": code,

internal/httpv2/handlers/redirect_handler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"short-link/internal/repo"
1414
"short-link/internal/service"
15+
"short-link/utils"
1516

1617
"github.com/gin-gonic/gin"
1718
)
@@ -37,7 +38,7 @@ func (h *RedirectHandler) Redirect(c *gin.Context) {
3738
ctx,
3839
c.Request.Host,
3940
code,
40-
c.ClientIP(),
41+
utils.GetRealIP(c.Request),
4142
c.GetHeader("User-Agent"),
4243
c.GetHeader("Referer"),
4344
)

middleware/logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func LoggerMiddleware() gin.HandlerFunc {
2222

2323
latency := time.Since(start)
2424
status := c.Writer.Status()
25-
clientIP := c.ClientIP()
25+
clientIP := utils.GetRealIP(c.Request)
2626

2727
utils.LogInfo("[%s] %s %s %d %v %s",
2828
method,

middleware/ratelimit.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package middleware
77
import (
88
"net/http"
99
"short-link/cache"
10+
"short-link/utils"
1011
"time"
1112

1213
"github.com/gin-gonic/gin"
@@ -20,7 +21,8 @@ func RateLimitMiddleware() gin.HandlerFunc {
2021
return func(c *gin.Context) {
2122
// 如果Redis可用,使用分布式限流
2223
if cache.RedisClient != nil {
23-
key := "rate_limit:" + c.ClientIP()
24+
realIP := utils.GetRealIP(c.Request)
25+
key := "rate_limit:" + realIP
2426
count, err := cache.RedisClient.Incr(cache.Ctx, key).Result()
2527
if err == nil {
2628
if count == 1 {

utils/ip.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* IP 工具
3+
* 处理代理链路的真实 IP 提取(X-Forwarded-For / X-Real-IP)
4+
* 实现 redo.md 2.5:代理链路真实 IP 处理
5+
*/
6+
package utils
7+
8+
import (
9+
"net"
10+
"net/http"
11+
"strings"
12+
)
13+
14+
// GetRealIP 从请求中提取真实客户端 IP
15+
// 优先级:X-Forwarded-For(取第一个) > X-Real-IP > RemoteAddr
16+
// 同时进行基本验证,防止 IP 伪造
17+
func GetRealIP(r *http.Request) string {
18+
// 1. 检查 X-Forwarded-For(可能包含多个 IP,用逗号分隔)
19+
forwardedFor := r.Header.Get("X-Forwarded-For")
20+
if forwardedFor != "" {
21+
// 取第一个 IP(最原始的客户端 IP)
22+
ips := strings.Split(forwardedFor, ",")
23+
if len(ips) > 0 {
24+
ip := strings.TrimSpace(ips[0])
25+
if isValidIP(ip) {
26+
return ip
27+
}
28+
}
29+
}
30+
31+
// 2. 检查 X-Real-IP
32+
realIP := r.Header.Get("X-Real-IP")
33+
if realIP != "" {
34+
ip := strings.TrimSpace(realIP)
35+
if isValidIP(ip) {
36+
return ip
37+
}
38+
}
39+
40+
// 3. 回退到 RemoteAddr
41+
ip, _, err := net.SplitHostPort(r.RemoteAddr)
42+
if err != nil {
43+
// 如果没有端口,直接使用 RemoteAddr
44+
return r.RemoteAddr
45+
}
46+
return ip
47+
}
48+
49+
// isValidIP 验证 IP 地址格式(基本验证,防止明显伪造)
50+
func isValidIP(ip string) bool {
51+
parsed := net.ParseIP(ip)
52+
if parsed == nil {
53+
return false
54+
}
55+
// 拒绝内网 IP(如果配置了信任代理,可以放宽此限制)
56+
// 这里仅做格式验证,不拒绝内网 IP(因为可能确实来自内网代理)
57+
return true
58+
}
59+

0 commit comments

Comments
 (0)