2 Commits

Author SHA1 Message Date
AliceDarkest b4fbc972f2 Merge: Login case-insensitiv + Passwort-Wiederholfeld
mindflux-ci / build (push) Successful in 27s
2026-06-30 20:04:08 +02:00
AliceDarkest 21c594445f Fix: Login case-insensitiv (Registrierung speichert lowercase, Login war case-sensitiv) + Passwort-Wiederholfeld
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:03:46 +02:00
6 changed files with 52 additions and 10 deletions
@@ -49,7 +49,7 @@ public class SecurityConfig {
@Bean @Bean
public UserDetailsService userDetailsService(UserRepository userRepo) { public UserDetailsService userDetailsService(UserRepository userRepo) {
return username -> { return username -> {
User user = userRepo.findByUsername(username) User user = userRepo.findByUsernameIgnoreCase(username)
.orElseThrow(() -> new UsernameNotFoundException("Benutzer nicht gefunden")); .orElseThrow(() -> new UsernameNotFoundException("Benutzer nicht gefunden"));
return org.springframework.security.core.userdetails.User return org.springframework.security.core.userdetails.User
@@ -24,17 +24,24 @@ public class RegisterController {
} }
@PostMapping("/register") @PostMapping("/register")
public String processRegistration(@ModelAttribute("user") User user, Model model) { public String processRegistration(@ModelAttribute("user") User user,
// Benutzername klein schreiben, um Doppelungen zu vermeiden @RequestParam(value = "confirmPassword", required = false) String confirmPassword) {
user.setUsername(user.getUsername().toLowerCase()); // Schreibweise des Benutzernamens beibehalten, nur trimmen
String username = user.getUsername() == null ? "" : user.getUsername().trim();
user.setUsername(username);
// Prüfen, ob Benutzername schon existiert // Passwörter müssen übereinstimmen (Schutz vor Tippfehlern bei der Eingabe)
if (userService.findByUsername(user.getUsername()) != null) { if (user.getPassword() == null || user.getPassword().isBlank()
// Zurück zur kombinierten Login/Signup-Seite mit geöffnetem Signup-Panel || !user.getPassword().equals(confirmPassword)) {
return "redirect:/?passwordMismatch";
}
// Benutzername schon vergeben? (case-insensitiv, damit "Alice" und "alice" nicht kollidieren)
if (username.isBlank() || userService.findByUsernameIgnoreCase(username) != null) {
return "redirect:/?registerError"; return "redirect:/?registerError";
} }
// Passwort verschlüsseln // Passwort verschlüsseln und speichern
user.setPassword(passwordEncoder.encode(user.getPassword())); user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole("USER"); // Standardrolle user.setRole("USER"); // Standardrolle
userService.save(user); userService.save(user);
@@ -9,6 +9,9 @@ public interface UserRepository extends JpaRepository<User, Long> {
// Benutzer anhand des Benutzernamens suchen // Benutzer anhand des Benutzernamens suchen
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);
// Case-insensitive Suche: Login und Registrierung sollen unabhängig von Groß-/Kleinschreibung greifen
Optional<User> findByUsernameIgnoreCase(String username);
// Benutzer anhand des Benutzernamens löschen // Benutzer anhand des Benutzernamens löschen
void deleteByUsername(String username); void deleteByUsername(String username);
} }
@@ -4,6 +4,7 @@ import com.example.mindflux.model.User;
public interface UserService { public interface UserService {
User findByUsername(String username); User findByUsername(String username);
User findByUsernameIgnoreCase(String username);
void save(User user); void save(User user);
void deleteByUsername(String username); // <-- Nur die Methodensignatur, ohne {} void deleteByUsername(String username); // <-- Nur die Methodensignatur, ohne {}
} }
@@ -25,6 +25,11 @@ public class UserServiceImpl implements UserService {
return userRepository.findByUsername(username).orElse(null); // <-- Optional aufgelöst return userRepository.findByUsername(username).orElse(null); // <-- Optional aufgelöst
} }
@Override
public User findByUsernameIgnoreCase(String username) {
return userRepository.findByUsernameIgnoreCase(username).orElse(null);
}
@Override @Override
public void save(User user) { public void save(User user) {
userRepository.save(user); userRepository.save(user);
+28 -2
View File
@@ -58,6 +58,7 @@
</div> </div>
<div th:if="${param.registerError}" class="mf-alert err">Benutzername bereits vergeben.</div> <div th:if="${param.registerError}" class="mf-alert err">Benutzername bereits vergeben.</div>
<div th:if="${param.passwordMismatch}" class="mf-alert err">Die Passwörter stimmen nicht überein. Bitte erneut eingeben.</div>
<form th:action="@{/register}" method="post"> <form th:action="@{/register}" method="post">
<div class="mf-field"> <div class="mf-field">
@@ -72,6 +73,11 @@
<label class="mf-label" for="reg-password">Passwort</label> <label class="mf-label" for="reg-password">Passwort</label>
<input id="reg-password" class="mf-input" type="password" name="password" placeholder="Passwort" required> <input id="reg-password" class="mf-input" type="password" name="password" placeholder="Passwort" required>
</div> </div>
<div class="mf-field">
<label class="mf-label" for="reg-password2">Passwort wiederholen</label>
<input id="reg-password2" class="mf-input" type="password" name="confirmPassword" placeholder="Passwort wiederholen" required>
</div>
<p id="reg-pw-msg" class="mf-alert err" style="display:none">Die Passwörter stimmen nicht überein.</p>
<button class="neon-btn" type="submit"><span class="lock">🚀</span> Account erstellen</button> <button class="neon-btn" type="submit"><span class="lock">🚀</span> Account erstellen</button>
</form> </form>
@@ -128,10 +134,30 @@
el.addEventListener('click', function (e) { e.preventDefault(); c.classList.remove('active'); }); el.addEventListener('click', function (e) { e.preventDefault(); c.classList.remove('active'); });
}); });
// Bei Registrierungsfehler direkt das Signup-Panel öffnen // Bei Registrierungs-/Passwortfehler direkt das Signup-Panel öffnen
if (new URLSearchParams(window.location.search).has('registerError')) { var qp = new URLSearchParams(window.location.search);
if (qp.has('registerError') || qp.has('passwordMismatch')) {
c.classList.add('active'); c.classList.add('active');
} }
// Client-seitiger Passwort-Abgleich (sofortiges Feedback; der Server prüft zusätzlich)
var pw = document.getElementById('reg-password');
var pw2 = document.getElementById('reg-password2');
var pwMsg = document.getElementById('reg-pw-msg');
var signupForm = pw2 ? pw2.closest('form') : null;
function pwCheck() {
var mismatch = pw2.value.length > 0 && pw.value !== pw2.value;
if (pwMsg) pwMsg.style.display = mismatch ? 'block' : 'none';
pw2.setCustomValidity(pw.value !== pw2.value ? 'Passwörter stimmen nicht überein' : '');
return !mismatch;
}
if (pw && pw2) {
pw.addEventListener('input', pwCheck);
pw2.addEventListener('input', pwCheck);
if (signupForm) signupForm.addEventListener('submit', function (e) {
if (pw.value !== pw2.value) { e.preventDefault(); pwCheck(); c.classList.add('active'); }
});
}
})(); })();
</script> </script>