dużo lepsza autoryzacja teraz, dużo lepsza. Tokeny wygasają, można mieć tylko jeden aktywny token per user

This commit is contained in:
2025-06-02 13:46:09 +02:00
parent 0d32b4a495
commit ffbd8d220c
4 changed files with 32 additions and 7 deletions

View File

@@ -27,7 +27,6 @@ public class SecurityConfig {
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll() .requestMatchers("/api/v1/auth/**").permitAll()
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()) .anyRequest().authenticated())
.sessionManagement(session -> session .sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

View File

@@ -5,6 +5,7 @@ import _11.asktpk.artisanconnectbackend.entities.Client;
import _11.asktpk.artisanconnectbackend.security.JwtUtil; import _11.asktpk.artisanconnectbackend.security.JwtUtil;
import _11.asktpk.artisanconnectbackend.service.ClientService; import _11.asktpk.artisanconnectbackend.service.ClientService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController @RestController
@RequestMapping("/api/v1/auth") @RequestMapping("/api/v1/auth")
public class AuthController { public class AuthController {
@@ -33,6 +35,7 @@ public class AuthController {
String token = jwtUtil.generateToken(client.getEmail(), userRole, userId); String token = jwtUtil.generateToken(client.getEmail(), userRole, userId);
log.info("Logged in as " + client.getEmail());
return ResponseEntity.status(HttpStatus.OK) return ResponseEntity.status(HttpStatus.OK)
.body(new AuthResponseDTO(userId, userRole, token)); .body(new AuthResponseDTO(userId, userRole, token));
} else { } else {
@@ -54,6 +57,8 @@ public class AuthController {
savedClient.getId() savedClient.getId()
); );
log.info("Registered as " + savedClient.getEmail());
return ResponseEntity.status(HttpStatus.CREATED) return ResponseEntity.status(HttpStatus.CREATED)
.body(new AuthResponseDTO( .body(new AuthResponseDTO(
savedClient.getId(), savedClient.getId(),

View File

@@ -36,8 +36,12 @@ public class JwtRequestFilter extends OncePerRequestFilter {
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7); jwt = authorizationHeader.substring(7);
if (jwtUtil.isBlacklisted(jwt)) { if (jwtUtil.isBlacklisted(jwt) || !jwtUtil.isLatestToken(jwt)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
String jsonResponse = "{\"error\": \"Token is invalid or expired. Please login again.\"}";
response.getWriter().write(jsonResponse);
return; return;
} }

View File

@@ -8,10 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.util.Date; import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
@@ -40,11 +37,27 @@ public class JwtUtil {
return Keys.hmacShaKeyFor(secret.getBytes()); return Keys.hmacShaKeyFor(secret.getBytes());
} }
private final Map<String, String> userActiveTokens = new ConcurrentHashMap<>();
public boolean isLatestToken(String token) {
String email = extractEmail(token);
String tokenId = extractTokenId(token);
String latestTokenId = userActiveTokens.get(email);
return latestTokenId != null && latestTokenId.equals(tokenId);
}
public String generateToken(String email, String role, Long userId) { public String generateToken(String email, String role, Long userId) {
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
claims.put("role", role); claims.put("role", role);
claims.put("userId", userId); claims.put("userId", userId);
return createToken(claims, email); claims.put("tokenId", UUID.randomUUID().toString());
String token = createToken(claims, email);
userActiveTokens.put(email, extractTokenId(token));
return token;
} }
private String createToken(Map<String, Object> claims, String subject) { private String createToken(Map<String, Object> claims, String subject) {
@@ -57,6 +70,10 @@ public class JwtUtil {
.compact(); .compact();
} }
public String extractTokenId(String token) {
return extractAllClaims(token).get("tokenId", String.class);
}
public String extractEmail(String token) { public String extractEmail(String token) {
return extractClaim(token, Claims::getSubject); return extractClaim(token, Claims::getSubject);
} }