无法保护托管 Blazor Web assembly 文件下载

问题描述 投票:0回答:1

我有一个托管的 blazor Web 程序集应用程序,我想在其中保护文件下载,以便只有具有正确角色/权限的用户才能从该文件夹下载文件。我不希望匿名用户能够从此文件夹下载任何文件。 这些文件位于 server/api 项目中名为

Uploads
的文件夹中,因此它们不在
wwwroot
文件夹中。

服务器管道如下所示:

...
app.UseAuthorization();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "Uploads")),
    RequestPath = "/SellerFiles"
});

//app.Map("", appBuilder =>
//{
//    appBuilder.UseFilter();

//    appBuilder.UseFileServer(new FileServerOptions
//    {
//        FileProvider = new PhysicalFileProvider($@"{Path.Combine(builder.Environment.ContentRootPath, "Uploads")}"),
//        RequestPath = new PathString("/SellerFiles"), //empty, because root path is in Map now
//        EnableDirectoryBrowsing = false
//    }); 
//});

app.MapControllers();
...

UseFilters
看起来像

public class FilterMiddleware
{
    private readonly RequestDelegate _next;

    public FilterMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        List<Claim> claims = httpContext.User.Claims.ToList();

        await _next(httpContext);
        //if (true)
        //{
        //    //proceed serving files
        //    await _next(httpContext);
        //}
        //else
        //{
        //    //return you custom response
        //    await httpContext.Response.WriteAsync("Forbidden");
        //}
    }
}

public static class FilterMiddlewareExtensions
{
    public static IApplicationBuilder UseFilter(this IApplicationBuilder applicationBuilder)
    {
        return applicationBuilder.UseMiddleware<FilterMiddleware>();
    }
}

当用户从

https://mywebsite.com/SellerFiles/...
请求文件时,如果他们经过身份验证,我想提供该文件,但如果他们是匿名的,则不提供该文件。

我尝试实现注释代码

app.Map UseFileServer
,但我无法正确配置它。我对它进行了很多尝试,当我向
public async Task InvokeAsync
发出请求时,我无法让它在
/SellerFiles
中达到断点。

现在,经过身份验证的用户可以通过调用

Uploads
/SellerFiles
文件夹下载文件,匿名用户也可以。

如何正确保护文件?

编辑 我想我已经找到了问题所在,但还没有完全解决。我遵循此处的指导:Microsoft 静态文件授权链接

我设置了授权回退策略,效果很好。我必须创建一些控制器方法AllowAnonymous。所以现在Uploads文件夹应该需要授权,但我在未登录时仍然可以下载文件。

builder.Services.AddAuthorizationCore(options =>
{    
    var type = typeof(Permissions);

    foreach (var permission in type.GetFields())
    {
        options.AddPolicy(
            permission.GetValue(null)?.ToString() ?? "",
            policyBuilder => policyBuilder.RequireAssertion(
                context => context.User.HasClaim(claim => claim.Type == "Permissions" && claim.Value == permission.GetValue(null)?.ToString())));
    }

    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();

});
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
    app.UseSwagger();
    app.UseSwaggerUI();
}
else
{
    app.UseResponseCompression();
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseCors("AllowAll");

app.UseAuthentication();

app.UseSerilogRequestLogging();

app.UseStaticFiles();

app.UseAuthorization();

app.UseBlazorFrameworkFiles();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.MapHangfireDashboard();
app.MapHub<SignalR>("/chathub");


using (var scope = app.Services.CreateScope())
{   
    var jobService = scope.ServiceProvider.GetRequiredService<IScheduleJobs>();
    jobService.Run();
    //do your stuff....
}

var options = new DashboardOptions
{
    Authorization = new IDashboardAuthorizationFilter[]
        {
            new HangfireDashboardJwtAuthorizationFilter(tokenValidationParameters, "Administrator")
        }
};
app.UseHangfireDashboard("/hangfire", options);

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
           Path.Combine(builder.Environment.ContentRootPath, "Uploads")),
    RequestPath = "/SellerFiles"
});

app.Run();

我错过了什么?

blazor-server-side middleware static-files
1个回答
0
投票

好吧,我解决了我的问题并想展示解决方案。我最终调用了一个 API 端点并将文件流式传输回客户端。然后我使用一个简单的 Javascript 函数创建一个

A tag
并调用下载。用户选择保存文件的位置。另外,我删除了对 Program.cs 所做的所有修改。

这是我的 razor.cs 代码,由用户单击下载按钮调用。

protected async Task DownloadFile(CartViewModel cartViewModel)
{
    FileDownload fileDownload = new FileDownload()
    {
        OrderId = cartViewModel.OrderId,
        SellerId = cartViewModel.Product?.SellerId,
        ZipFileName = cartViewModel.Product?.ZipFileName
    };

    if(OrderService != null)
    {
        bool response = await OrderService.DownloadFileStream(fileDownload, $"{ApiEndpoints.File}/download/{cartViewModel.Product?.SellerId}/{cartViewModel.OrderId}", JSruntime);

        if (response)
        {
            SnackbarService?.Add("File Downloaded Successfully", Severity.Success);
        }
        else
        {
            SnackbarService?.Add("Problem Downloading File", Severity.Error);
        }
    }            
}

这是我的 OrderService.DownloadFileStream 方法。

public async Task<bool> DownloadFileStream(FileDownload model, string endPoint, IJSRuntime JSruntime)
{
    try
    {
        var user = System.Text.Json.JsonSerializer.Serialize(model);
        var requestContent = new StringContent(user, Encoding.UTF8, "application/json");

        if (!await GetBearerToken())
        {
            return false;
        }

        Stream stream = await client.GetStreamAsync(endPoint);


        byte[] bytes;
        using (var memoryStream = new MemoryStream())
        {
            stream.CopyTo(memoryStream);
            bytes = memoryStream.ToArray();
        }
        string base64 = Convert.ToBase64String(bytes);
        await JSruntime.InvokeAsync<object>("saveAsFile", model.ZipFileName, base64);


    }
    catch (ApiException exception)
    {
        return false;
    }

    return true;
}

这是我的控制器端点。

public async Task<MemoryStream> Get([FromRoute] string SellerId, [FromRoute] int OrderId)
{
    string filePath = string.Empty;

    DirectoryInfo d = new DirectoryInfo(@$"Uploads\{SellerId}\{OrderId}\");            

    FileInfo[] files = d.GetFiles();

    // There should only be 1 file in this folder
    foreach (FileInfo fileInfo in files)
    {
        if (fileInfo.Extension.ToLower().Trim() == ".zip")
        {
            filePath = @$"Uploads\{SellerId}\{OrderId}\{fileInfo.Name}";
        }
    }

    if(filePath == string.Empty)
    {
        return new MemoryStream();
    }
    
    //converting Pdf file into bytes array
    var dataBytes = await System.IO.File.ReadAllBytesAsync(filePath);
    //adding bytes to memory stream
    var dataStream = new MemoryStream(dataBytes);

    return dataStream;
}

这是 Javascript 函数。

function saveAsFile(filename, bytesBase64) {
    var link = document.createElement('a');
    link.download = filename;
    link.href = "data:application/octet-stream;base64," + bytesBase64;
    document.body.appendChild(link); // Needed for Firefox
    link.click();
    document.body.removeChild(link);
}

由于文件位于 wwwroot 之外的 Uploads 文件夹中,用户无法访问

https://mywebsite.com/Uploads
或任何子文件夹,因此我的文件受到保护。我计划在控制器端点中添加检查,以确保用户确实有权下载该文件,即他们已付费。

© www.soinside.com 2019 - 2024. All rights reserved.