| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- using System.Security.Claims;
- namespace Library.Extensions;
- public static class HttpContextExtension
- {
- // 회원 ID 조회
- public static int GetMemberID(this HttpContext context)
- {
- return int.TryParse(context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value, out var id) ? id : 0;
- }
- // 회원 이름 조회
- public static string? UserName(this HttpContext context) => context.User.Identity?.Name;
- // 이전 요청 URL 조회
- public static string? GetReferer(this HttpContext context)
- {
- if (context.Request.Headers.TryGetValue("Referer", out var referer) && !string.IsNullOrWhiteSpace(referer))
- {
- return referer.ToString();
- }
- return null;
- }
- // Client IP 조회 — Cloudflare → IIS → backend reverse proxy 환경 대응
- // 우선순위:
- // 0) X-Original-Client-IP (Next.js proxy 가 Cloudflare 더블 경유 우회용 커스텀 헤더 ⇒ 진짜 user IP)
- // 1) CF-Connecting-IP (Cloudflare 가 보장하는 client IP, 단 더블 경유 시 Next.js server IP)
- // 2) X-Real-IP (일부 proxy 가 채움)
- // 3) X-Forwarded-For (comma-separated 의 첫 값만)
- // 4) RemoteIpAddress (ForwardedHeaders middleware 가 채워주는 경우)
- // IPv4-mapped IPv6 (`::ffff:127.0.0.1`) → `127.0.0.1` 로 정규화.
- public static string? GetClientIP(this HttpContext context)
- {
- // 0순위: Next.js Route Handler 가 명시 forward 한 user IP
- // (developers/app/api/.../route.ts 가 cf-connecting-ip 추출 후 이 헤더로 backend 전달)
- if (context.Request.Headers.TryGetValue("X-Original-Client-IP", out var orig) && !string.IsNullOrWhiteSpace(orig))
- {
- return NormalizeIP(orig.ToString().Trim());
- }
- if (context.Request.Headers.TryGetValue("CF-Connecting-IP", out var cf) && !string.IsNullOrWhiteSpace(cf))
- {
- return NormalizeIP(cf.ToString().Trim());
- }
- if (context.Request.Headers.TryGetValue("X-Real-IP", out var xri) && !string.IsNullOrWhiteSpace(xri))
- {
- return NormalizeIP(xri.ToString().Trim());
- }
- var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
- if (!string.IsNullOrWhiteSpace(forwardedFor))
- {
- // comma-separated 의 가장 왼쪽 = 원래 client (RFC 7239 / standard)
- var first = forwardedFor.Split(',', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
- if (!string.IsNullOrWhiteSpace(first))
- {
- return NormalizeIP(first);
- }
- }
- return NormalizeIP(context.Connection.RemoteIpAddress?.ToString());
- }
- private static string? NormalizeIP(string? ip)
- {
- if (string.IsNullOrWhiteSpace(ip))
- {
- return null;
- }
- // IPv4-mapped IPv6 (e.g. "::ffff:127.0.0.1") → IPv4 만 추출
- if (System.Net.IPAddress.TryParse(ip, out var parsed))
- {
- if (parsed.IsIPv4MappedToIPv6)
- {
- return parsed.MapToIPv4().ToString();
- }
- return parsed.ToString();
- }
- return ip;
- }
- // User-agent 조회 — Next.js Route Handler 가 forward 한 X-Original-User-Agent 최우선.
- public static string? GetUserAgent(this HttpContext context)
- {
- if (context.Request.Headers.TryGetValue("X-Original-User-Agent", out var origUa) && !string.IsNullOrWhiteSpace(origUa))
- {
- return origUa.ToString();
- }
- if (context.Request.Headers.TryGetValue("User-Agent", out var userAgent) && !string.IsNullOrWhiteSpace(userAgent))
- {
- return userAgent.ToString();
- }
- return null;
- }
- // 상관관계 ID 조회
- public static string GetCorrelationID(this HttpContext context) => context.TraceIdentifier;
- }
|