.net core 大文件上传

本文详细介绍了在.NET Core中处理大文件上传时的安全注意事项和解决方案,包括使用流式处理减少内存消耗、验证文件扩展名、大小和内容安全,以及设置上传限制。同时讨论了数据库、物理存储和云存储的不同存储方案,并提供了代码示例来演示如何实现安全的文件上传。
摘要由CSDN通过智能技术生成

.net core 大文件上传

注意事项

在用户上传文件时,有可能会遭受到攻击:

  • 执行拒绝服务攻击
  • 上传病毒或恶意软件
  • 以其他方式破坏网络和服务器

降低成功攻击可能性的安全措施如下:

  1. 将文件上传到专用文件上传区域,最好是非系统驱动器。 使用专用位置便于对上传的文件实施安全限制。 禁用对文件上传位置的执行权限;
  2. 保存文件的目录应与程序所在目录不一致
  3. 使用应用确定的安全的文件名,不使用用户提供的文件名或上传的文件的不受信任的文件名;
  4. 限制文件的扩展名
  5. 验证是否对服务器执行客户端检查。客户端检查易于规避;
  6. 限制上传文件的大小
  7. 文件不应该被具有相同名称的上传文件覆盖时,应先从数据库或物理存储上检查文件名,再上传文件;
  8. 先对上传的内容运行病毒/恶意软件扫描程序,然后再存储文件。

解决方案

存储方案

数据库存储:

  • 对于小型文件上传,数据库通常快于物理存储;
  • 数据库比物理存储更为方便,因为可以在查询用户数据时同事可以查询文件(例如用户头像)
  • 本地数据库存储比云数据库存储成本要低

物理存储:

  • 对于大型文件上传,数据库限制可能会限制上传的大小。
  • 物理存储比数据库存储成本更高
  • 进程需对存储位置有读写权限。切勿授予执行权限。

云数据存储服务

  • 服务通常通过本地解决方案提供提升的可伸缩性和复原能力,而它们往往受单一故障点的影响
  • 在大型存储基础结构方案中,服务的成本可能更低。

文件上传方案

缓冲

先将整个文件保存到内存,然后通过IFormFile得到stream。如果并发文件上传的数量和大小超过了内存的容量,网站就会因为内存不足而崩溃。

流式处理

将一个stream分多个Section读取并写入,无需将整个请求放入内存。流式传输无法显著提高性能。 流式传输可降低上传文件时对内存或磁盘空间的需求。

.net core 流式处理示例

完整代码:github代码路径

修改Kestrel最大请求正文限制

由于Kestrel最大请求正文限制在28.6M,在Program.cs里更改最大请求正文限制(bytes)。

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions => {
    serverOptions.Limits.MaxRequestBodySize = 524288000;
});

流式传输到物理位置的完整方法

[HttpPost]
[Route("upload")]
public async Task<IActionResult> Upload()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File",
            "无法处理该请求(ContentType不是Multipart).");
        _logger.LogError("无法处理该请求(ContentType不是Multipart).");
        return BadRequest();
    }

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader =
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            // 如果存在表单数据,立即失败并返回。
            if (!MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File", $"无法处理该请求(ContentDisposition不是form-data,或文件名为空)");
                _logger.LogError("无法处理该请求(ContentDisposition不是form-data,或文件名为空)");
                return BadRequest(ModelState);
            }
            else
            {
                // 不要相信客户端发送的文件名。 要显示文件名,请对值进行 HTML 编码。
                var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);
                var trustedFileNameForFileStorage = Path.GetRandomFileName();

                // **警告!**
                // 在以下文件处理方法中,不会扫描文件的内容。
                // 在大多数生产场景中,在将文件提供给用户或其他系统之前,
                // 会在文件上使用防病毒/反恶意软件扫描程序 API。

                var streamedFileContent = await FileHelpers.ProcessStreamedFile(
                    section, contentDisposition, ModelState,
                    _permittedExtensions, _fileSizeLimit, _logger);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }

                using (var targetStream = System.IO.File.Create(
                    Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
                {
                    await targetStream.WriteAsync(streamedFileContent);

                    _logger.LogInformation(
                        "文件'{TrustedFileNameForDisplay}' 保存在 " +
                        "'{TargetFilePath}' 作为 {TrustedFileNameForFileStorage}",
                        trustedFileNameForDisplay, _targetFilePath,
                        trustedFileNameForFileStorage);
                }
            }
        }

        // 读取下一节
        section = await reader.ReadNextSectionAsync();
    }

    return Created(nameof(FileUploadController), null);
}

文件内容验证示例

文件扩展名验证

需验证文件的扩展名,将不能上传的文件类型排除在外。

var ext = Path.GetExtension(fileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
    return false;
}
文件签名验证

文件的签名由文件开头部分中的前几个字节确定。 可以使用这些字节指示扩展名是否与文件内容匹配。 下面是常见压缩文件的示例。

private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
{
    { ".zip", new List<byte[]>
        {
        new byte[] { 0x50, 0x4B, 0x03, 0x04 },
            new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
            new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
            new byte[] { 0x50, 0x4B, 0x05, 0x06 },
            new byte[] { 0x50, 0x4B, 0x07, 0x08 },
            new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
        }
    },
    { ".rar", new List<byte[]> { new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07,0x00 } } },
    { ".7z", new List<byte[]>{ new byte[] { 0x37,0x7A,0xBC,0xAF,0x27,0x1C } } },
}

using (var reader = new BinaryReader(data))
{
    // 文件签名检查
    var signatures = _fileSignature[ext];
    var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
}

文件名安全

尽量不要使用客户端提供的文件名,作为文件存储的文件名。使用 Path.GetRandomFileNamePath.GetTempFileName 为文件创建安全的文件名。
如果文件重名的情况下,不覆盖文件,需要在数据库中查询文件是否重名。

大小验证

限制上传的文件的大小。
appsetting.json文件内容

{
  "FileSizeLimit": 524288000
}

文件大小超出限制时,将拒绝文件

if (memoryStream.Length > sizeLimit)
{
    // 文件过大的处理...
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
.NET Core 中,可以通过以下方式配置上传文件的大小限制: 1. 在 Startup.cs 文件中,找到 ConfigureServices 方法,在方法中添加以下代码: ``` services.Configure<IISServerOptions>(options => { options.MaxRequestBodySize = 104857600; // 100MB }); ``` 其中,MaxRequestBodySize 属性表示上传文件的最大限制,以字节为单位。 2. 如果是使用 Kestrel 服务器,则可以在 Program.cs 文件中的 CreateHostBuilder 方法中添加以下代码: ``` public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseKestrel(options => { options.Limits.MaxRequestBodySize = 104857600; // 100MB }); webBuilder.UseStartup<Startup>(); }); ``` 其中,MaxRequestBodySize 属性同样表示上传文件的最大限制,以字节为单位。 需要注意的是,以上两种方式可以同时存在,但是以 Kestrel 配置为准。另外,在配置文件中也可以进行上传文件大小的配置,例如 appsettings.json 文件中: ``` { "AllowedHosts": "*", "MaxAllowedContentLength": 104857600 // 100MB } ``` 其中,MaxAllowedContentLength 属性同样表示上传文件的最大限制,以字节为单位。这种方式需要在 Startup.cs 文件中的 ConfigureServices 方法中添加以下代码: ``` services.Configure<IISServerOptions>(options => { options.MaxAllowedContentLength = Configuration.GetValue<long>("MaxAllowedContentLength"); }); ``` 需要引入 Microsoft.Extensions.Configuration 包。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr___Ray

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值