基本IP保护 Swagger UI 的中间件

实现说明

  1. 配置文件(appsettings.json)

    • 集中管理 Swagger 相关配置,包括允许访问的 IP 列表、文档标题和版本
    • 支持环境特定配置(如 appsettings.Development.json),方便多环境部署
  2. SwaggerSettings 类

    • 强类型绑定配置文件中的 Swagger 节点
    • 提供类型安全的配置访问,避免硬编码字符串键
  3. SwaggerIPFilterMiddleware 中间件

    • 自动拦截 Swagger 相关路径(/swagger 和 /swagger-ui)
    • 支持获取真实客户端 IP(包括反向代理场景)
    • 处理 IP 格式清洗(移除端口号,兼容 IPv4 和 IPv6)
    • 结合日志记录未授权访问尝试
  4. Program.cs 配置

    • 启用配置文件热重载(reloadOnChange: true)
    • 通过 IOptions 模式注入配置,自动感知配置变化
    • 提供扩展方法简化中间件注册

使用方法

  1. 修改 appsettings.json 中的 Swagger:AllowedIPs 添加允许访问的 IP
  2. 无需重启应用,配置会自动生效
  3. 非允许 IP 访问 Swagger 时会收到 403 禁止访问响应。

1、Program.cs

cs 复制代码
using SwaggerIpFilterDemo;

var builder = WebApplication.CreateBuilder(args);

// 配置:启用配置文件热重载
builder.Configuration
    .SetBasePath(builder.Environment.ContentRootPath)
    .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", reloadOnChange: true, optional: true)
    .AddEnvironmentVariables();

// 注册Swagger配置
builder.Services.Configure<SwaggerSettings>(builder.Configuration.GetSection("Swagger"));

// 添加控制器服务
builder.Services.AddControllers();

// 添加Swagger服务
builder.Services.AddSwaggerGen(c =>
{
    var swaggerSettings = builder.Configuration.GetSection("Swagger").Get<SwaggerSettings>();
    c.SwaggerDoc(swaggerSettings.Version, new() { Title = swaggerSettings.Title, Version = swaggerSettings.Version });
});

var app = builder.Build();

// 开发环境启用详细异常页
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

// 注册Swagger IP过滤中间件(必须在UseSwagger之前)
app.UseSwaggerIPFilter();

// 启用Swagger
app.UseSwagger();
app.UseSwaggerUI(c =>
{
    var swaggerSettings = app.Services.GetRequiredService<IOptions<SwaggerSettings>>().Value;
    c.SwaggerEndpoint($"/swagger/{swaggerSettings.Version}/swagger.json", swaggerSettings.Title);
});

// 其他中间件
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();

app.MapControllers();

app.Run();

2、SwaggerIPFilterMiddleware.cs

cs 复制代码
namespace SwaggerIpFilterDemo;

/// <summary>
/// Swagger IP访问过滤中间件
/// </summary>
public class SwaggerIPFilterMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IOptions<SwaggerSettings> _swaggerSettings;
    private readonly ILogger<SwaggerIPFilterMiddleware> _logger;

    /// <summary>
    /// 构造函数
    /// </summary>
    public SwaggerIPFilterMiddleware(
        RequestDelegate next,
        IOptions<SwaggerSettings> swaggerSettings,
        ILogger<SwaggerIPFilterMiddleware> logger)
    {
        _next = next;
        _swaggerSettings = swaggerSettings;
        _logger = logger;
    }

    /// <summary>
    /// 中间件执行逻辑
    /// </summary>
    public async Task InvokeAsync(HttpContext context)
    {
        // 只拦截Swagger相关请求
        if (context.Request.Path.StartsWithSegments("/swagger") || 
            context.Request.Path.StartsWithSegments("/swagger-ui"))
        {
            // 获取客户端真实IP(支持反向代理场景)
            var clientIp = GetClientIpAddress(context);
            
            // 处理IP格式(移除端口号)
            var cleanIp = CleanIpAddress(clientIp);
            
            // 检查是否为允许的IP
            var allowedIps = _swaggerSettings.Value.AllowedIPs;
            var isAllowed = allowedIps.Contains(cleanIp) || 
                           cleanIp == "127.0.0.1" || 
                           cleanIp == "::1";

            if (!isAllowed)
            {
                _logger.LogWarning("Swagger访问被拒绝,IP: {Ip}", cleanIp);
                context.Response.StatusCode = StatusCodes.Status403Forbidden;
                await context.Response.WriteAsync($"禁止访问Swagger文档,您的IP: {cleanIp}");
                return;
            }
        }

        // 允许访问,继续处理后续中间件
        await _next(context);
    }

    /// <summary>
    /// 获取客户端真实IP(支持反向代理)
    /// </summary>
    private string GetClientIpAddress(HttpContext context)
    {
        // 优先从反向代理头获取(如Nginx/Apache设置的X-Forwarded-For)
        var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
        if (!string.IsNullOrEmpty(forwardedFor))
        {
            return forwardedFor.Split(',', StringSplitOptions.TrimEntries).First();
        }

        // 直接从连接获取IP
        return context.Connection.RemoteIpAddress?.ToString() ?? "未知IP";
    }

    /// <summary>
    /// 清理IP地址(移除端口号)
    /// </summary>
    private string CleanIpAddress(string ip)
    {
        if (string.IsNullOrEmpty(ip)) return ip;
        
        // 处理带端口的IP(如 "192.168.1.100:5000" → "192.168.1.100")
        if (ip.Contains(':'))
        {
            // 区分IPv6(如"fe80::1:2:3:4:5")和带端口的IPv4
            var parts = ip.Split(':');
            return parts.Length > 2 ? ip : parts[0];
        }

        return ip;
    }
}

/// <summary>
/// 中间件扩展方法,简化注册
/// </summary>
public static class SwaggerIPFilterMiddlewareExtensions
{
    public static IApplicationBuilder UseSwaggerIPFilter(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<SwaggerIPFilterMiddleware>();
    }
}

3、SwaggerSettings.cs

cs 复制代码
namespace SwaggerIpFilterDemo;

/// <summary>
/// Swagger配置选项
/// </summary>
public class SwaggerSettings
{
    /// <summary>
    /// 允许访问Swagger的IP列表
    /// </summary>
    public List<string> AllowedIPs { get; set; } = new List<string>();
    
    /// <summary>
    /// 文档标题
    /// </summary>
    public string Title { get; set; } = "API文档";
    
    /// <summary>
    /// 文档版本
    /// </summary>
    public string Version { get; set; } = "v1";
}

4、appsettings.json

cs 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Swagger": {
    "AllowedIPs": [
      "127.0.0.1",    // 本地IPv4
      "::1",          // 本地IPv6
      "192.168.1.100",
      "192.168.1.101"
    ],
    "Title": "API文档",
    "Version": "v1"
  }
}
相关推荐
whltaoin5 小时前
【Java 微服务中间件】RabbitMQ 全方位解析:同步异步对比、SpringAMQT基础入门、实战、交换机类型及消息处理详解
spring boot·微服务·中间件·rabbitmq·spring amqt
小程故事多_801 天前
LangChain1.0系列:中间件深度解析,让 AI智能体上下文控制不失控
人工智能·中间件·langchain
q***75601 天前
【Golang】——Gin 框架中间件详解:从基础到实战
中间件·golang·gin
9ilk1 天前
【基于one-loop-per-thread的高并发服务器】--- 项目测试
运维·服务器·c++·后端·中间件
小二李2 天前
第7章 Node框架实战篇 - Express 中间件与RESTful API 接口规范
中间件·express
百***46452 天前
SocketTool、串口调试助手、MQTT中间件基础
单片机·嵌入式硬件·中间件
zhangbaolin2 天前
深度智能体的中间件
中间件·langchain·大模型·深度智能体
whltaoin2 天前
【微服务中间件】RabbitMQ 多平台安装搭建实践指南(Windows_macOS_Ubuntu_Docker 全场景)
微服务·中间件·消息队列·rabbitmq·多平台
q***71853 天前
开源数据同步中间件(Dbsyncer)简单玩一下 mysql to mysql 的增量,全量配置
mysql·中间件·开源
Alex艾力的IT数字空间3 天前
完整事务性能瓶颈分析案例:支付系统事务雪崩优化
开发语言·数据结构·数据库·分布式·算法·中间件·php