Bildordner pro Benutzer in ASP.NET Core

Standardmäßig zeigt die Galerie allen dieselben Ordner, was für eine Unternehmenswebsite passt. In einem Forum oder einer App für mehrere Kunden sollte jede Person nur ihre eigenen Bilder sehen. In den ASP.NET-Core-Beispielen ist das eine einzige Methode, RootFor(HttpContext), die entscheidet, wo die Dateien eines Requests liegen. Diese Seite zeigt die Variante pro Benutzer, die Anmeldung, die sie braucht, und was öffentlich bleibt.
Eine Methode entscheidet
Jede Route (auflisten, Ordner anlegen, hochladen) fragt RootFor nach einem Ordner auf der Festplatte und der URL, unter der er ausgeliefert wird. Sonst weiß nichts im Code etwas von Benutzern.
// Where this request's files live. This is the only method to change for per-user folders.
// "PerUserFolders": false (default) everyone shares <content root>/uploads, served at /uploads
// "PerUserFolders": true each signed-in user gets uploads/users/<user id>
public static RteRoot RootFor(HttpContext http)
{
var uploads = UploadsFolder(http.RequestServices.GetRequiredService<IWebHostEnvironment>());
if (!http.RequestServices.GetRequiredService<IConfiguration>().GetValue<bool>("PerUserFolders"))
return new RteRoot(uploads, "/uploads");
// Use the user's id (a GUID with ASP.NET Core Identity), not the user name, which can change.
var id = http.User.FindFirstValue(ClaimTypes.NameIdentifier)
?? throw new RteError(401, "Sign in to use the image gallery.");
// The id becomes a folder name, so it must pass the same rules as any other folder name.
if (!IsValidName(id)) throw new RteError(403, "This user id cannot be used as a folder name.");
return new RteRoot(Path.Combine(uploads, "users", id), "/uploads/users/" + Uri.EscapeDataString(id));
}PerUserFoldersinappsettings.jsonist standardmäßigfalse. Steht der Wert auftrue, erhält jeder angemeldete Benutzeruploads/users/<id>/als Stammordner seiner Galerie. Seine Schaltfläche „New folder“ und seine Uploads arbeiten darin, und..kann ihn nicht verlassen, weil die Ordnerprüfung gegen diesen Stammordner läuft.- Die ID ist
ClaimTypes.NameIdentifier. Mit ASP.NET Core Identity enthält dieser Claim die ID des Benutzers, eine GUID, die sich nie ändert. Ein Benutzername oder eine E-Mail-Adresse kann sich ändern, und unter Windows wärenAliceundalicederselbe Ordner. - Kein Benutzer, keine Galerie. Ein anonymer Request erhält
401 {"error":"Sign in to use the image gallery."}, und der Dialog zeigt seine eigene Meldung „The image gallery is unavailable right now (http-401).“ statt des Textes des Servers. - Die ID wird wie ein Ordnername geprüft. Eine ID wie
../../xerhält ein 403 statt eines Pfads.
Anmeldung
Das Beispiel verwendet Cookie-Authentifizierung, das Schema, mit dem auch ASP.NET Core Identity anmeldet. Das eigene Login Ihrer App ersetzt die Testanmeldung unten.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// Your app's sign-in goes here. "PerUserFolders": true in appsettings.json gives each user a folder.
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
var app = builder.Build();
app.UseStaticFiles();
app.UseRteStaticFiles(); // /uploads, /richtexteditor, /rte-files.js
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute(); // HomeController.Index at "/", plus RteController's [Route] attributesDamit Sie Ordner pro Benutzer ausprobieren können, ohne ein Login zu bauen, bildet das Beispiel eine reine Testanmeldung ab, und zwar nur in der Umgebung Development. Sie meldet jeden als jeden an und darf deshalb niemals auf einen echten Server gelangen:
// TEST ONLY, and mapped only in Development: /demo/sign-in?user=alice signs anyone in as anyone,
// so per-user folders can be tried without a real login. Never ship this.
if (app.Environment.IsDevelopment())
{
app.MapGet("/demo/sign-in", async (HttpContext http, string user) =>
{
var identity = new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, user)], CookieAuthenticationDefaults.AuthenticationScheme);
await http.SignInAsync(new ClaimsPrincipal(identity));
return Results.Redirect("/");
});
}Mit einfachem dotnet run, das als Production läuft, antwortete /demo/sign-in mit 404.
Was öffentlich bleibt
Getrennte Galerien machen die Dateien nicht privat. /uploads/users/alice/harbor-fd5ff24b.jpg wird über dieselbe Zuordnung für statische Dateien ausgeliefert wie jeder andere Upload und antwortete auf einen Request ohne Cookie mit 200. Für Bilder in veröffentlichten Inhalten ist das meist richtig: Leser eines Beitrags müssen seine Bilder laden können. Der zufällige Teil des Dateinamens sorgt dafür, dass niemand die URL erraten kann, aber jeder, mit dem sie geteilt wird, kann sie öffnen. Wenn die Bilder eines Benutzers privat bleiben müssen, liefern Sie sie über einen Endpoint aus, der den Benutzer prüft, statt über UseStaticFiles. Das Beispiel enthält keinen solchen Endpoint.
Das Einschalten von Ordnern pro Benutzer entscheidet auch nicht darüber, wer überhaupt hochladen darf. Ergänzen Sie [Authorize] am Controller oder .RequireAuthorization() an der Minimal-API-Gruppe, sobald die App eine Anmeldung hat.
Testen
Starten Sie das Beispielprojekt zu dieser Anleitung in Development mit eingeschalteten Ordnern pro Benutzer und arbeiten Sie dann mit zwei Cookie-Dateien als zwei Benutzer:
dotnet run -- --environment Development --PerUserFolders true
curl "http://localhost:5302/rte/gallery?folder="
{"error":"Sign in to use the image gallery."}
curl -s -o /dev/null -c alice.jar "http://localhost:5302/demo/sign-in?user=alice"
curl -b alice.jar -F "file=@harbor.jpg" http://localhost:5302/rte/upload
{"url":"/uploads/users/alice/harbor-fd5ff24b.jpg"}
curl -s -o /dev/null -c bob.jar "http://localhost:5302/demo/sign-in?user=bob"
curl -b bob.jar -F "file=@forest.jpg" http://localhost:5302/rte/upload
{"url":"/uploads/users/bob/forest-8b1a0f16.jpg"}
curl -b bob.jar "http://localhost:5302/rte/gallery?folder="
{"currentFolder":"","currentFolderDisplay":"/","parentFolder":null,"folders":[],"images":[{"url":"/uploads/users/bob/forest-8b1a0f16.jpg","name":"forest-8b1a0f16.jpg","folder":"","size":36337}]}Bobs Galerie listet nur sein Bild auf. Öffnen Sie im Browser http://localhost:5302/demo/sign-in?user=alice und klicken Sie auf Insert Gallery: Der Dialog zeigt nur Alices Bilder und Ordner. Das Bild oben auf dieser Seite zeigt den Ordner uploads danach, mit users/alice und users/bob neben den gemeinsamen Ordnern, die das Beispiel verwendet, solange PerUserFolders ausgeschaltet ist.
Dieselbe Anleitung für andere Plattformen
JavaScript · PHP · MVC 5 · Web Forms