我有一个托管的 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();
我错过了什么?
好吧,我解决了我的问题并想展示解决方案。我最终调用了一个 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
或任何子文件夹,因此我的文件受到保护。我计划在控制器端点中添加检查,以确保用户确实有权下载该文件,即他们已付费。