| @@ -7,8 +7,8 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> | |||||
| <PackageReference Include="Serilog.Sinks.Seq" Version="6.0.0" /> | |||||
| <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> | |||||
| <PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -0,0 +1,85 @@ | |||||
| using System.Net; | |||||
| using Altob.NtuInvoiceGateway.Models; | |||||
| using Microsoft.Extensions.Options; | |||||
| namespace Altob.NtuInvoiceGateway.Middleware; | |||||
| public class IpWhitelistMiddleware | |||||
| { | |||||
| private readonly RequestDelegate _next; | |||||
| private readonly ILogger<IpWhitelistMiddleware> _logger; | |||||
| private readonly IpWhitelistOptions _ipWhitelistOptions; | |||||
| public IpWhitelistMiddleware( | |||||
| RequestDelegate next, | |||||
| ILogger<IpWhitelistMiddleware> logger, | |||||
| IOptions<IpWhitelistOptions> ipWhitelistOptions) | |||||
| { | |||||
| _next = next; | |||||
| _logger = logger; | |||||
| _ipWhitelistOptions = ipWhitelistOptions.Value; | |||||
| } | |||||
| public async Task InvokeAsync(HttpContext context) | |||||
| { | |||||
| // 如果 IP 白名單未啟用,直接通過 | |||||
| if (!_ipWhitelistOptions.Enabled) | |||||
| { | |||||
| await _next(context); | |||||
| return; | |||||
| } | |||||
| // 只對 Test 開頭的頁面進行 IP 白名單檢查 | |||||
| var path = context.Request.Path.Value ?? string.Empty; | |||||
| if (!path.StartsWith("/Test", StringComparison.OrdinalIgnoreCase)) | |||||
| { | |||||
| await _next(context); | |||||
| return; | |||||
| } | |||||
| var remoteIp = context.Connection.RemoteIpAddress; | |||||
| var clientIp = GetClientIp(context); | |||||
| _logger.LogDebug("IP Whitelist check for Test page - Path: {Path}, RemoteIp: {RemoteIp}, ClientIp: {ClientIp}", | |||||
| path, remoteIp, clientIp); | |||||
| // 檢查 RemoteIp 或 ClientIp 是否在白名單中 | |||||
| var isAllowed = false; | |||||
| if (remoteIp != null && IsIpAllowed(remoteIp.ToString())) | |||||
| { | |||||
| isAllowed = true; | |||||
| } | |||||
| else if (clientIp != null && IsIpAllowed(clientIp)) | |||||
| { | |||||
| isAllowed = true; | |||||
| } | |||||
| if (!isAllowed) | |||||
| { | |||||
| _logger.LogWarning("{ServiceName} - {ActionName} - {CallerIp} - {ClientIp} - IP access denied to Test page: {Path}", | |||||
| "NtuInvoiceGateway", "IpWhitelistCheck", remoteIp?.ToString() ?? "Unknown", clientIp, path); | |||||
| context.Response.StatusCode = StatusCodes.Status403Forbidden; | |||||
| await context.Response.WriteAsync("Access denied. Your IP address is not allowed to access test pages."); | |||||
| return; | |||||
| } | |||||
| await _next(context); | |||||
| } | |||||
| private bool IsIpAllowed(string ipAddress) | |||||
| { | |||||
| return _ipWhitelistOptions.AllowedIps.Contains(ipAddress); | |||||
| } | |||||
| private string? GetClientIp(HttpContext context) | |||||
| { | |||||
| var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault(); | |||||
| if (!string.IsNullOrEmpty(forwardedFor)) | |||||
| { | |||||
| return forwardedFor.Split(',').FirstOrDefault()?.Trim(); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Altob.NtuInvoiceGateway.Models; | |||||
| public class InvoiceApiOptions | |||||
| { | |||||
| public string Endpoint { get; set; } = string.Empty; | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| namespace Altob.NtuInvoiceGateway.Models; | |||||
| public class IpWhitelistOptions | |||||
| { | |||||
| public bool Enabled { get; set; } | |||||
| public List<string> AllowedIps { get; set; } = new(); | |||||
| } | |||||
| @@ -1,3 +1,5 @@ | |||||
| using Altob.NtuInvoiceGateway.Middleware; | |||||
| using Altob.NtuInvoiceGateway.Models; | |||||
| using Serilog; | using Serilog; | ||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||
| @@ -19,6 +21,8 @@ builder.Services.Configure<Altob.NtuInvoiceGateway.Models.CompanyInfo>( | |||||
| builder.Configuration.GetSection("CompanyInfo")); | builder.Configuration.GetSection("CompanyInfo")); | ||||
| builder.Services.Configure<Altob.NtuInvoiceGateway.Models.InvoiceApiOptions>( | builder.Services.Configure<Altob.NtuInvoiceGateway.Models.InvoiceApiOptions>( | ||||
| builder.Configuration.GetSection("InvoiceApi")); | builder.Configuration.GetSection("InvoiceApi")); | ||||
| builder.Services.Configure<IpWhitelistOptions>( | |||||
| builder.Configuration.GetSection("IpWhitelist")); | |||||
| var app = builder.Build(); | var app = builder.Build(); | ||||
| @@ -36,6 +40,8 @@ try | |||||
| app.UseRouting(); | app.UseRouting(); | ||||
| app.UseMiddleware<IpWhitelistMiddleware>(); | |||||
| app.UseAuthorization(); | app.UseAuthorization(); | ||||
| app.MapStaticAssets(); | app.MapStaticAssets(); | ||||
| @@ -0,0 +1,36 @@ | |||||
| { | |||||
| "DetailedErrors": false, | |||||
| "Logging": { | |||||
| "LogLevel": { | |||||
| "Default": "Warning", | |||||
| "Microsoft.AspNetCore": "Error" | |||||
| } | |||||
| }, | |||||
| "Serilog": { | |||||
| "Using": [ "Serilog.Sinks.Seq" ], | |||||
| "MinimumLevel": { | |||||
| "Default": "Information", | |||||
| "Override": { | |||||
| "Microsoft": "Warning", | |||||
| "System": "Warning" | |||||
| } | |||||
| }, | |||||
| "WriteTo": [ | |||||
| { | |||||
| "Name": "Seq", | |||||
| "Args": { | |||||
| "serverUrl": "http://localhost:5341", | |||||
| "apiKey": "" | |||||
| } | |||||
| } | |||||
| ], | |||||
| "Enrich": [ "FromLogContext" ] | |||||
| }, | |||||
| "IpWhitelist": { | |||||
| "Enabled": true, | |||||
| "AllowedIps": [ "127.0.0.1", "::1","61.219.172.82","61.222.237.97","125.227.77.79" ] | |||||
| }, | |||||
| "InvoiceApi": { | |||||
| "Endpoint": "http://140.112.143.162:22055/api/Intella/invoiceInfo" | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| { | |||||
| "DetailedErrors": false, | |||||
| "Logging": { | |||||
| "LogLevel": { | |||||
| "Default": "Warning", | |||||
| "Microsoft.AspNetCore": "Error" | |||||
| } | |||||
| }, | |||||
| "Serilog": { | |||||
| "Using": [ "Serilog.Sinks.Seq" ], | |||||
| "MinimumLevel": { | |||||
| "Default": "Information", | |||||
| "Override": { | |||||
| "Microsoft": "Warning", | |||||
| "System": "Warning" | |||||
| } | |||||
| }, | |||||
| "WriteTo": [ | |||||
| { | |||||
| "Name": "Seq", | |||||
| "Args": { | |||||
| "serverUrl": "http://localhost:5341", | |||||
| "apiKey": "" | |||||
| } | |||||
| } | |||||
| ], | |||||
| "Enrich": [ "FromLogContext" ] | |||||
| }, | |||||
| "IpWhitelist": { | |||||
| "Enabled": true, | |||||
| "AllowedIps": [ "127.0.0.1", "::1" ] | |||||
| }, | |||||
| "InvoiceApi": { | |||||
| "Endpoint": "http://192.168.110.72:22055/api/Intella/invoiceInfo" | |||||
| } | |||||
| } | |||||
| @@ -25,6 +25,10 @@ | |||||
| ], | ], | ||||
| "Enrich": [ "FromLogContext" ] | "Enrich": [ "FromLogContext" ] | ||||
| }, | }, | ||||
| "IpWhitelist": { | |||||
| "Enabled": true, | |||||
| "AllowedIps": [ "127.0.0.1", "::1" ] | |||||
| }, | |||||
| "AllowedHosts": "*", | "AllowedHosts": "*", | ||||
| "CompanyInfo": { | "CompanyInfo": { | ||||
| "Name": "國立台灣大學臨時停車場", | "Name": "國立台灣大學臨時停車場", | ||||