From fc98e5aad16443ef99414bbf3e01c5b091bad022 Mon Sep 17 00:00:00 2001 From: AliceDarkest Date: Fri, 26 Jun 2026 15:22:25 +0200 Subject: [PATCH] Profilbild-Upload (413 + Auto-Skalierung) und CSRF aktivieren - application.properties: multipart max-file/request-size auf 10MB (nginx client_max_body_size muss serverseitig passend gesetzt sein) - ProfileController: hochgeladene Profilbilder werden serverseitig auf max. 400px herunterskaliert und als JPEG normalisiert gespeichert (statt der Originaldatei roh); nicht lesbare Bilder werden ignoriert - SecurityConfig: CSRF aktiviert; Spiele-JSON-APIs (/api/games, /api/techtacular, /api/mindmatch) ausgenommen; GET-Logout erlaubt, da die Logout-Links in den Templates GET sind Co-Authored-By: Claude Opus 4.8 (1M context) --- .../mindflux/config/SecurityConfig.java | 9 ++- .../controller/ProfileController.java | 57 +++++++++++++------ src/main/resources/application.properties | 8 +++ 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/example/mindflux/config/SecurityConfig.java b/src/main/java/com/example/mindflux/config/SecurityConfig.java index 95a4617..3804633 100644 --- a/src/main/java/com/example/mindflux/config/SecurityConfig.java +++ b/src/main/java/com/example/mindflux/config/SecurityConfig.java @@ -10,6 +10,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.userdetails.*; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration public class SecurityConfig { @@ -31,10 +32,16 @@ public class SecurityConfig { .permitAll() ) .logout(logout -> logout + // Logout-Links in den Templates sind GET -> GET-Logout erlauben, sonst 404/Fehler unter CSRF + .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) .logoutSuccessUrl("/?logout") .permitAll() ) - .csrf(csrf -> csrf.disable()); // optional deaktivieren bei Custom-Logik/API-Nutzung + .csrf(csrf -> csrf + // Spiele-APIs werden per fetch (application/json) ohne Token aufgerufen -> ausnehmen. + // Reine JSON-Endpunkte sind nicht über klassische HTML-Formulare CSRF-ausnutzbar. + .ignoringRequestMatchers("/api/games/**", "/api/techtacular/**", "/api/mindmatch/**") + ); return http.build(); } diff --git a/src/main/java/com/example/mindflux/controller/ProfileController.java b/src/main/java/com/example/mindflux/controller/ProfileController.java index 991232d..93413e3 100644 --- a/src/main/java/com/example/mindflux/controller/ProfileController.java +++ b/src/main/java/com/example/mindflux/controller/ProfileController.java @@ -14,6 +14,11 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import javax.imageio.ImageIO; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -99,26 +104,42 @@ public class ProfileController { if (file != null && !file.isEmpty()) { try { - // Altes Profilbild löschen (falls vorhanden) - if (existingUser.getProfilePicture() != null && !existingUser.getProfilePicture().isEmpty()) { - Path oldFilePath = Paths.get(System.getProperty("user.dir"), "uploads", "profiles", existingUser.getProfilePicture()); - if (Files.exists(oldFilePath)) { - Files.delete(oldFilePath); + BufferedImage original = ImageIO.read(file.getInputStream()); + if (original == null) { + // Keine lesbare Bilddatei -> Upload ignorieren, restliches Profil trotzdem speichern. + System.out.println("Profilbild-Upload ignoriert: keine lesbare Bilddatei."); + } else { + // Auf max. Kantenlänge herunterskalieren (kein Hochskalieren). + final int MAX_DIM = 400; + int w = original.getWidth(); + int h = original.getHeight(); + double scale = Math.min(1.0, (double) MAX_DIM / Math.max(w, h)); + int nw = Math.max(1, (int) Math.round(w * scale)); + int nh = Math.max(1, (int) Math.round(h * scale)); + + BufferedImage scaled = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB); + Graphics2D g = scaled.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setColor(Color.WHITE); // JPEG kennt kein Alpha -> Transparenz auf weißem Grund + g.fillRect(0, 0, nw, nh); + g.drawImage(original, 0, 0, nw, nh, null); + g.dispose(); + + // Altes Profilbild löschen (falls vorhanden) + if (existingUser.getProfilePicture() != null && !existingUser.getProfilePicture().isEmpty()) { + Path oldFilePath = Paths.get(System.getProperty("user.dir"), "uploads", "profiles", existingUser.getProfilePicture()); + Files.deleteIfExists(oldFilePath); } + + // Normalisiert als JPEG speichern + String fileName = UUID.randomUUID() + ".jpg"; + Path uploadPath = Paths.get(UPLOAD_DIR); + Files.createDirectories(uploadPath); + ImageIO.write(scaled, "jpg", uploadPath.resolve(fileName).toFile()); + + existingUser.setProfilePicture(fileName); } - - // Neues Profilbild speichern - String fileExtension = getFileExtension(file.getOriginalFilename()); - String fileName = UUID.randomUUID() + fileExtension; - - Path uploadPath = Paths.get(UPLOAD_DIR); - Files.createDirectories(uploadPath); - - Path filePath = uploadPath.resolve(fileName); - file.transferTo(filePath.toFile()); - - existingUser.setProfilePicture(fileName); - } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c61bbed..f45cee3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -41,3 +41,11 @@ spring.output.ansi.enabled=ALWAYS # Öffentliche Basis-URL, z. B. für künftig erzeugte absolute Links. mindflux.public-url=${MINDFLUX_PUBLIC_URL:http://localhost:8282} + +# ======================= +# Datei-Uploads (Profilbilder) +# ======================= +# Hinweis: Der nginx-Reverse-Proxy muss client_max_body_size passend gesetzt haben, +# sonst greift schon dort ein 413, bevor die App den Upload sieht. +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB -- 2.52.0