Invalidate auth tokens

This commit is contained in:
2026-03-12 18:16:20 -06:00
parent 8fdd28cbbd
commit 9dcb4865fa
4 changed files with 61 additions and 33 deletions

View File

@@ -14,6 +14,7 @@ import com.petshop.backend.repository.EmployeeStoreRepository;
import com.petshop.backend.repository.UserRepository; import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.JwtUtil; import com.petshop.backend.security.JwtUtil;
import com.petshop.backend.service.UserBusinessLinkageService; import com.petshop.backend.service.UserBusinessLinkageService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -22,8 +23,6 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -137,11 +136,7 @@ public class AuthController {
@GetMapping("/me") @GetMapping("/me")
public ResponseEntity<UserInfoResponse> getCurrentUser() { public ResponseEntity<UserInfoResponse> getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = getAuthenticatedUser();
String username = authentication.getName();
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
EmployeeStore employeeStore = resolveEmployeeStore(user); EmployeeStore employeeStore = resolveEmployeeStore(user);
@@ -159,11 +154,8 @@ public class AuthController {
@PutMapping("/me") @PutMapping("/me")
public ResponseEntity<?> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) { public ResponseEntity<?> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = getAuthenticatedUser();
String username = authentication.getName(); boolean invalidateToken = false;
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) { if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) {
if (userRepository.findByUsername(request.getUsername()).isPresent()) { if (userRepository.findByUsername(request.getUsername()).isPresent()) {
@@ -172,6 +164,7 @@ public class AuthController {
return ResponseEntity.status(HttpStatus.CONFLICT).body(error); return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
} }
user.setUsername(request.getUsername()); user.setUsername(request.getUsername());
invalidateToken = true;
} }
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
@@ -189,6 +182,11 @@ public class AuthController {
if (request.getPassword() != null && !request.getPassword().isEmpty()) { if (request.getPassword() != null && !request.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword())); user.setPassword(passwordEncoder.encode(request.getPassword()));
invalidateToken = true;
}
if (invalidateToken) {
user.setTokenVersion(user.getTokenVersion() + 1);
} }
User updatedUser = userRepository.save(user); User updatedUser = userRepository.save(user);
@@ -219,11 +217,7 @@ public class AuthController {
@PostMapping("/me/avatar") @PostMapping("/me/avatar")
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) { public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = getAuthenticatedUser();
String username = authentication.getName();
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
if (file.isEmpty()) { if (file.isEmpty()) {
Map<String, String> error = new HashMap<>(); Map<String, String> error = new HashMap<>();
@@ -275,11 +269,7 @@ public class AuthController {
@GetMapping("/me/avatar") @GetMapping("/me/avatar")
public ResponseEntity<?> getAvatar() { public ResponseEntity<?> getAvatar() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = getAuthenticatedUser();
String username = authentication.getName();
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) { if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) {
Map<String, String> error = new HashMap<>(); Map<String, String> error = new HashMap<>();
@@ -294,11 +284,7 @@ public class AuthController {
@DeleteMapping("/me/avatar") @DeleteMapping("/me/avatar")
public ResponseEntity<?> deleteAvatar() { public ResponseEntity<?> deleteAvatar() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = getAuthenticatedUser();
String username = authentication.getName();
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) { if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) {
try { try {
@@ -322,4 +308,12 @@ public class AuthController {
response.put("note", "Token remains valid until expiration. Clear token from client storage."); response.put("note", "Token remains valid until expiration. Clear token from client storage.");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }
private User getAuthenticatedUser() {
try {
return AuthenticationHelper.getAuthenticatedUser(userRepository);
} catch (RuntimeException ex) {
throw new UsernameNotFoundException(ex.getMessage(), ex);
}
}
} }

View File

@@ -9,12 +9,11 @@ import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.UserRepository; import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatRealtimeService;
import com.petshop.backend.service.ChatService; import com.petshop.backend.service.ChatService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -37,9 +36,11 @@ public class ChatController {
} }
private User getCurrentUser() { private User getCurrentUser() {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); try {
return userRepository.findByUsername(userDetails.getUsername()) return AuthenticationHelper.getAuthenticatedUser(userRepository);
.orElseThrow(() -> new UsernameNotFoundException("User not found")); } catch (RuntimeException ex) {
throw new UsernameNotFoundException(ex.getMessage(), ex);
}
} }
@PostMapping("/conversations") @PostMapping("/conversations")

View File

@@ -68,14 +68,23 @@ public class UserService {
User user = userRepository.findById(id) User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
boolean invalidateToken =
!user.getUsername().equals(request.getUsername())
|| user.getRole() != request.getRole()
|| !user.getActive().equals(request.getActive() != null ? request.getActive() : true);
user.setUsername(request.getUsername()); user.setUsername(request.getUsername());
if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword())); user.setPassword(passwordEncoder.encode(request.getPassword()));
invalidateToken = true;
} }
user.setFullName(request.getFullName()); user.setFullName(request.getFullName());
user.setEmail(request.getEmail()); user.setEmail(request.getEmail());
user.setRole(request.getRole()); user.setRole(request.getRole());
user.setActive(request.getActive() != null ? request.getActive() : true); user.setActive(request.getActive() != null ? request.getActive() : true);
if (invalidateToken) {
user.setTokenVersion(user.getTokenVersion() + 1);
}
user = userRepository.save(user); user = userRepository.save(user);
return mapToResponse(user); return mapToResponse(user);

View File

@@ -6,6 +6,7 @@ import com.petshop.backend.entity.User;
import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.EmployeeRepository;
import com.petshop.backend.repository.UserRepository; import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.AppPrincipal;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -13,11 +14,34 @@ import org.springframework.stereotype.Component;
@Component @Component
public class AuthenticationHelper { public class AuthenticationHelper {
public static User getAuthenticatedUser(UserRepository userRepository) { public static Authentication getAuthentication() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
throw new RuntimeException("No authenticated user found"); throw new RuntimeException("No authenticated user found");
} }
return authentication;
}
public static AppPrincipal getAuthenticatedPrincipal() {
Object principal = getAuthentication().getPrincipal();
if (principal instanceof AppPrincipal appPrincipal) {
return appPrincipal;
}
throw new RuntimeException("Authenticated principal is not supported");
}
public static Long getAuthenticatedUserId() {
return getAuthenticatedPrincipal().getUserId();
}
public static User getAuthenticatedUser(UserRepository userRepository) {
Authentication authentication = getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof AppPrincipal appPrincipal) {
return userRepository.findById(appPrincipal.getUserId())
.orElseThrow(() -> new RuntimeException("User not found: " + appPrincipal.getUserId()));
}
String username = authentication.getName(); String username = authentication.getName();
return userRepository.findByUsername(username) return userRepository.findByUsername(username)