forked from baron/baron-sso
세션 IP 표시와 로그아웃 처리 보강
This commit is contained in:
87
backend/internal/utils/client_ip.go
Normal file
87
backend/internal/utils/client_ip.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ResolveClientIP selects the best client IP from proxy headers and the remote address.
|
||||
// It prefers a public IP from X-Forwarded-For, then X-Real-IP, and finally the remote IP.
|
||||
func ResolveClientIP(forwardedFor, realIP, remoteIP string) string {
|
||||
forwardedCandidates := splitClientIPs(forwardedFor)
|
||||
if ip := firstPublicIP(forwardedCandidates); ip != "" {
|
||||
return ip
|
||||
}
|
||||
if ip := normalizeIP(realIP); ip != "" && !IsPrivateOrReservedIP(ip) {
|
||||
return ip
|
||||
}
|
||||
if ip := normalizeIP(remoteIP); ip != "" && !IsPrivateOrReservedIP(ip) {
|
||||
return ip
|
||||
}
|
||||
if len(forwardedCandidates) > 0 {
|
||||
return forwardedCandidates[0]
|
||||
}
|
||||
if ip := normalizeIP(realIP); ip != "" {
|
||||
return ip
|
||||
}
|
||||
return normalizeIP(remoteIP)
|
||||
}
|
||||
|
||||
// IsPrivateOrReservedIP reports whether the IP is private or from a non-public network range.
|
||||
func IsPrivateOrReservedIP(raw string) bool {
|
||||
ip := net.ParseIP(strings.TrimSpace(raw))
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
|
||||
return true
|
||||
}
|
||||
for _, cidr := range []string{
|
||||
"100.64.0.0/10",
|
||||
"fc00::/7",
|
||||
} {
|
||||
_, network, err := net.ParseCIDR(cidr)
|
||||
if err == nil && network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func splitClientIPs(forwardedFor string) []string {
|
||||
if strings.TrimSpace(forwardedFor) == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(forwardedFor, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if ip := normalizeIP(part); ip != "" {
|
||||
result = append(result, ip)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func firstPublicIP(candidates []string) string {
|
||||
for _, candidate := range candidates {
|
||||
if !IsPrivateOrReservedIP(candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeIP(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
if host, _, err := net.SplitHostPort(raw); err == nil {
|
||||
raw = host
|
||||
}
|
||||
ip := net.ParseIP(raw)
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
return ip.String()
|
||||
}
|
||||
24
backend/internal/utils/client_ip_test.go
Normal file
24
backend/internal/utils/client_ip_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResolveClientIP_PrefersPublicForwardedIP(t *testing.T) {
|
||||
got := ResolveClientIP("100.100.100.1, 203.0.113.25, 10.0.0.2", "", "172.18.0.5")
|
||||
if got != "203.0.113.25" {
|
||||
t.Fatalf("expected public forwarded IP, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveClientIP_FallsBackToFirstForwardedWhenAllPrivate(t *testing.T) {
|
||||
got := ResolveClientIP("100.100.100.1, 10.0.0.2", "192.168.0.10", "172.18.0.5")
|
||||
if got != "100.100.100.1" {
|
||||
t.Fatalf("expected first forwarded private IP, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveClientIP_PrefersPublicRealIPOverPrivateForwarded(t *testing.T) {
|
||||
got := ResolveClientIP("100.100.100.1, 10.0.0.2", "198.51.100.7", "172.18.0.5")
|
||||
if got != "198.51.100.7" {
|
||||
t.Fatalf("expected public real IP, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user