You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6.0 ExceptionHandlerMiddleware:如何修复错误信息泄露问题?

ASP.NET Core 6.0 Web API 异常中间件安全问题处理

原异常处理中间件代码

public class ExceptionHandlerMiddleware
{
        private readonly RequestDelegate next;

        public ExceptionHandlerMiddleware(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await this.next(context).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                await this.ConvertExceptionAsync(context, ex).ConfigureAwait(false);
            }
        }

        private Task ConvertExceptionAsync(HttpContext context, Exception exception)
        {
            HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;

            context.Response.ContentType = "application/json";

            var errorResponse = new ErrorResponse();

            var result = string.Empty;

            switch (exception)
            {
                case ValidationException validationException:
                    httpStatusCode = HttpStatusCode.BadRequest;
                    errorResponse.Error.Errors = JsonSerializer.Serialize(validationException.ValidationErrors);
                    break;

                case BadRequestException badRequestException:
                    httpStatusCode = HttpStatusCode.BadRequest;
                    errorResponse.Error.Message = badRequestException.Message;
                    break;

                case NotFoundException notFoundException:
                    httpStatusCode = HttpStatusCode.NotFound;
                    break;

                case NotAcceptableException notAcceptableException:
                    httpStatusCode = HttpStatusCode.NotAcceptable;
                    errorResponse.Error.Message = notAcceptableException.Message;
                    break;

                case Exception ex:
                    httpStatusCode = HttpStatusCode.BadRequest;
                    break;
            }

            errorResponse.ApiVersion = context.GetRequestedApiVersion()?.ToString();
            errorResponse.Error.Code = (int)httpStatusCode;
            context.Response.StatusCode = (int)httpStatusCode;

            if (string.IsNullOrEmpty(result))
            {
                errorResponse.Error.Message = exception.Message;
            }

            var serializeOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                WriteIndented = true,
            };

            result = JsonSerializer.Serialize(errorResponse, serializeOptions);

            return context.Response.WriteAsync(result);
        }
}

原错误响应格式

{
  "error": {
    "code": 404,
    "message": "Education (a65f7f0c-2a29-4da0-bd4b-d737320730c6) is not found",
    "errors": "[]"
  },
  "apiVersion": "1.0"
}

安全扫描报告问题

该文件处理Exception或运行时错误ex。在异常处理代码中,应用在ConvertExceptionAsync方法中通过WriteAsync暴露了异常详情

避免通过错误信息泄露数据

  1. 不要向用户暴露异常输出数据
  2. 妥善处理各方法的异常
  3. 配置全局处理器以防止未处理错误

疑问

  1. 应该请求安全团队抑制该问题,还是可以在应用层面解决?
  2. 更新:将context.Response.WriteAsync移至finally块(代码如下),是否能解决该安全问题?

修改后的代码

public async Task InvokeAsync(HttpContext context)
{
    bool isExceptionReported = false;
    string result = string.Empty;

    try
    {
        await this.next(context).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        isExceptionReported = true;
        result = this.ConvertException(context, ex);
    }
    finally
    {
        if (isExceptionReported)
        {
           await context.Response.WriteAsync(result).ConfigureAwait(false);
        }
    }
}

private string ConvertException(HttpContext context, Exception exception)
{
    HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;

    context.Response.ContentType = "application/json";

    var errorResponse = new ErrorResponse();

    var result = string.Empty;

    switch (exception)
    {
        case ValidationException validationException:
            httpStatusCode = HttpStatusCode.BadRequest;
            errorResponse.Error.Errors = JsonSerializer.Serialize(validationException.ValidationErrors);
            break;
        case BadRequestException badRequestException:
            httpStatusCode = HttpStatusCode.BadRequest;
            errorResponse.Error.Message = badRequestException.Message;
            break;
        case NotFoundException notFoundException:
            httpStatusCode = HttpStatusCode.NotFound;
            break;
        case NotAcceptableException notAcceptableException:
            httpStatusCode = HttpStatusCode.NotAcceptable;
            errorResponse.Error.Message = notAcceptableException.Message;
            break;
        case Exception ex:
            httpStatusCode = HttpStatusCode.BadRequest;
            break;
    }

    errorResponse.ApiVersion = context.GetRequestedApiVersion()?.ToString();
    errorResponse.Error.Code = (int)httpStatusCode;
    context.Response.StatusCode = (int)httpStatusCode;

    if (string.IsNullOrEmpty(result))
    {
        errorResponse.Error.Message = exception.Message;
    }

    var serializeOptions = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true,
    };

    result = JsonSerializer.Serialize(errorResponse, serializeOptions);

    return result;
}

解答

1. 不能请求安全团队抑制,必须在应用层面修复

安全扫描指出的是敏感信息泄露风险,属于可修复的代码缺陷,抑制问题会留下安全隐患,不符合安全规范。

2. 当前修改无法解决安全问题

把WriteAsync移到finally块只是改变了响应写入的时机,并没有改变异常信息直接返回给客户端的核心逻辑,通用Exception的Message依然可能包含内部敏感数据(如数据库错误、服务内部路径等),风险依然存在。

正确修复方案

(1)区分环境返回错误信息

注入IWebHostEnvironment,根据环境决定返回的错误内容:

  • 开发环境:返回详细异常信息,方便调试
  • 生产环境:只返回通用提示,不暴露任何内部细节

(2)完善异常分支处理

  • 自定义异常(如BadRequestException、NotFoundException):返回业务相关的友好提示,但避免泄露敏感ID或内部细节
  • 通用Exception:统一返回"服务器内部错误",同时将详细异常信息记录到日志(如Serilog、NLog),供排查问题使用

(3)优化错误响应内容

  • 非必要情况下,移除apiVersion这类可能暴露系统细节的字段
  • 确保Errors字段只返回客户端需要的验证信息,不包含内部错误堆栈

修复后的示例代码(核心部分)

// 新增构造注入IWebHostEnvironment和ILogger
private readonly RequestDelegate next;
private readonly IWebHostEnvironment _env;
private readonly ILogger<ExceptionHandlerMiddleware> _logger;

public ExceptionHandlerMiddleware(RequestDelegate next, IWebHostEnvironment env, ILogger<ExceptionHandlerMiddleware> logger)
{
    this.next = next;
    _env = env;
    _logger = logger;
}

private string ConvertException(HttpContext context, Exception exception)
{
    HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
    context.Response.ContentType = "application/json";
    var errorResponse = new ErrorResponse();

    switch (exception)
    {
        case ValidationException validationException:
            httpStatusCode = HttpStatusCode.BadRequest;
            errorResponse.Error.Errors = JsonSerializer.Serialize(validationException.ValidationErrors);
            errorResponse.Error.Message = "请求参数验证失败";
            break;
        case BadRequestException badRequestException:
            httpStatusCode = HttpStatusCode.BadRequest;
            errorResponse.Error.Message = badRequestException.Message;
            break;
        case NotFoundException notFoundException:
            httpStatusCode = HttpStatusCode.NotFound;
            errorResponse.Error.Message = "请求资源不存在";
            break;
        case NotAcceptableException notAcceptableException:
            httpStatusCode = HttpStatusCode.NotAcceptable;
            errorResponse.Error.Message = notAcceptableException.Message;
            break;
        case Exception ex:
            httpStatusCode = HttpStatusCode.InternalServerError;
            // 生产环境返回通用消息,开发环境返回原异常消息
            errorResponse.Error.Message = _env.IsProduction() ? "服务器内部错误" : ex.Message;
            // 记录详细异常日志
            _logger.LogError(ex, "未处理的异常");
            break;
    }

    errorResponse.Error.Code = (int)httpStatusCode;
    context.Response.StatusCode = (int)httpStatusCode;

    var serializeOptions = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true,
    };

    return JsonSerializer.Serialize(errorResponse, serializeOptions);
}

内容的提问来源于stack exchange,提问作者One Developer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 16:40:14