返回提交历史
Added
XFEToolBox.Server.Core/Utilities/ForwardedClientIpResolver.cs
+48
-0
Modified
XFEToolBox.Server.Test/Program.cs
+33
-1
Modified
XFEToolBox.Server/Program.cs
+6
-2
XFEstudio/XFEToolBox
新增 ForwardedClientIpResolver 及相关测试
新增 ForwardedClientIpResolver 工具类,支持从受信任代理请求头解析真实客户端 IP,并规范化处理。Program.cs 中获取客户端 IP 的逻辑改为调用该工具类,提升安全性。补充了多种场景下的测试用例,并完善了 using 指令。
26de6c1
代码差异
3 个文件
+87
-3
@@ -0,0 +1,48 @@
1
using System.Net;
2
3
namespace XFEToolBox.Server.Core.Utilities;
4
5
/// <summary>
6
/// Resolves the client address forwarded by a trusted reverse proxy.
7
/// </summary>
8
public static class ForwardedClientIpResolver
9
{
10
/// <summary>
11
/// Resolves an EdgeOne client address, falling back to the transport peer address.
12
/// The origin must only accept traffic from the trusted proxy before forwarded
13
/// headers can be treated as authoritative.
14
/// </summary>
15
public static string Resolve(
16
string connectionIp,
17
string? edgeOneConnectingIp,
18
string? forwardedFor)
19
{
20
if (TryNormalize(edgeOneConnectingIp, out var clientIp))
21
return clientIp;
22
23
if (!string.IsNullOrWhiteSpace(forwardedFor))
24
{
25
var addresses = forwardedFor.Split(
26
',',
27
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
28
if (addresses.Length > 0 && TryNormalize(addresses[^1], out clientIp))
29
return clientIp;
30
}
31
32
return TryNormalize(connectionIp, out clientIp) ? clientIp : connectionIp;
33
}
34
35
private static bool TryNormalize(string? value, out string address)
36
{
37
if (IPAddress.TryParse(value?.Trim(), out var parsed))
38
{
39
address = parsed.IsIPv4MappedToIPv6
40
? parsed.MapToIPv4().ToString()
41
: parsed.ToString();
42
return true;
43
}
44
45
address = string.Empty;
46
return false;
47
}
48
}
@@ -27,7 +27,8 @@ var tests = new (string Name, Action Run)[]
27
27
("私密群号具备足够长度并限制账户枚举", ChatGroupNumbersAreStrongAndRateLimited),
28
28
("聊天附件分块传输校验完整性和访问权限", ChatAttachmentTransferIsAuthorizedAndVerified),
29
29
("实时票据绑定用途且只能消费一次", ChatRealtimeTicketIsAudienceBoundAndSingleUse),
30
("实时票据限制每用户和全局待使用数量", ChatRealtimeTicketLimitsAreEnforced)
30
("实时票据限制每用户和全局待使用数量", ChatRealtimeTicketLimitsAreEnforced),
31
("EdgeOne 回源请求可解析真实客户端 IP", ForwardedClientIpIsResolved)
31
32
};
32
33
33
34
var failed = 0;
@@ -450,6 +451,37 @@ static void ChatRealtimeTicketLimitsAreEnforced()
450
451
Assert(store.OutstandingTicketCount == 3, "消费票据后没有释放全局配额。");
451
452
}
452
453
454
static void ForwardedClientIpIsResolved()
455
{
456
Assert(
457
ForwardedClientIpResolver.Resolve(
458
"10.0.0.8",
459
"203.0.113.21",
460
"198.51.100.7, 203.0.113.20") == "203.0.113.21",
461
"没有优先使用 EdgeOne 提供的 EO-Connecting-IP。");
462
463
Assert(
464
ForwardedClientIpResolver.Resolve(
465
"10.0.0.8",
466
null,
467
"198.51.100.7, 203.0.113.20") == "203.0.113.20",
468
"没有使用 X-Forwarded-For 中 EdgeOne 追加的最右侧地址。");
469
470
Assert(
471
ForwardedClientIpResolver.Resolve(
472
"::ffff:192.0.2.10",
473
"not-an-ip",
474
"also-invalid") == "192.0.2.10",
475
"非法转发请求头没有回退到连接 IP。");
476
477
Assert(
478
ForwardedClientIpResolver.Resolve(
479
"192.0.2.10",
480
null,
481
"198.51.100.7, not-an-ip") == "192.0.2.10",
482
"X-Forwarded-For 最右侧值无效时不应信任左侧可伪造地址。");
483
}
484
453
485
static void WithChatRepository(Action<ChatRepository, string> test)
454
486
{
455
487
var root = Path.Combine(Path.GetTempPath(), "XFEToolBox.Server.Chat.Test", Guid.NewGuid().ToString("N"));
@@ -3,6 +3,7 @@ using XFEToolBox.Core.Models.Users;
3
3
using XFEToolBox.Server.Core.Chat;
4
4
using XFEToolBox.Server.Core.Options;
5
5
using XFEToolBox.Server.Core.Services;
6
using XFEToolBox.Server.Core.Utilities;
6
7
using XFEToolBox.Server.Profiles;
7
8
using XFEToolBox.Server.Profiles.Data;
8
9
using XFEToolBox.Server.Realtime;
@@ -125,8 +126,11 @@ var server = XFEServerBuilder.CreateBuilder()
125
126
options.AcceptGet = true;
126
127
options.AcceptPost = true;
127
128
options.AcceptNonStandardJson = true;
128
// 只有在可信反向代理层完成源地址校验时才应采用 X-Forwarded-For。
129
options.GetIPFunction = static args => args.ClientIP;
129
// 源站必须只允许可信 EdgeOne 节点回源,防止客户端绕过代理伪造请求头。
130
options.GetIPFunction = static args => ForwardedClientIpResolver.Resolve(
131
args.ClientIP,
132
args.RequestHeaders["EO-Connecting-IP"],
133
args.RequestHeaders["X-Forwarded-For"]);
130
134
options.BindIP(ServerProfile.HttpAddress);
131
135
options.MainEntryPoint = "api";
132
136
options.ServerCoreName = "XFEToolBoxServer";