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

View File

@@ -68,14 +68,23 @@ public class UserService {
User user = userRepository.findById(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());
if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword()));
invalidateToken = true;
}
user.setFullName(request.getFullName());
user.setEmail(request.getEmail());
user.setRole(request.getRole());
user.setActive(request.getActive() != null ? request.getActive() : true);
if (invalidateToken) {
user.setTokenVersion(user.getTokenVersion() + 1);
}
user = userRepository.save(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.EmployeeRepository;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.AppPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@@ -13,11 +14,34 @@ import org.springframework.stereotype.Component;
@Component
public class AuthenticationHelper {
public static User getAuthenticatedUser(UserRepository userRepository) {
public static Authentication getAuthentication() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
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();
return userRepository.findByUsername(username)