From c5ff741f8cf34605d7ae27546de4e92c1104fd7f Mon Sep 17 00:00:00 2001 From: Kshitij <160704796+kshitij-ka@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:59:29 +0530 Subject: [PATCH] Refactor JWT config and enhance security, improve file download, and fix refresh token cleanup - Restricted public auth endpoints to only /login and /register in SecurityConfig - Added contentLength header and improved error response in FileController download API - Refactored JwtService to load secret key and expiration from application properties - Improved signing key handling using Base64 decoding - Updated RefreshTokenRepository with @Transactional @Modifying delete query - Ensured proper refresh token cleanup with flush() in RefreshTokenService - Annotated refresh token methods with @Transactional for consistency --- .../config/SecurityConfig.java | 2 +- .../controller/FileController.java | 4 +++- .../repository/RefreshTokenRepository.java | 6 ++++++ .../skycrateBackend/services/JwtService.java | 21 +++++++++++-------- .../services/RefreshTokenService.java | 7 +++++-- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/config/SecurityConfig.java b/src/main/java/com/skycrate/backend/skycrateBackend/config/SecurityConfig.java index 7cabb16..e0c6317 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/config/SecurityConfig.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/config/SecurityConfig.java @@ -29,7 +29,7 @@ public class SecurityConfig { .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(authenticationProvider) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/**", "/actuator/**").permitAll() + .requestMatchers("/api/auth/login", "/api/auth/register", "/actuator/**").permitAll() .requestMatchers(HttpMethod.GET, "/public/**").permitAll() .anyRequest().authenticated() ) diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/controller/FileController.java b/src/main/java/com/skycrate/backend/skycrateBackend/controller/FileController.java index e26a354..583094e 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/controller/FileController.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/controller/FileController.java @@ -53,10 +53,12 @@ public class FileController { return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .contentLength(decryptedData.length) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(decryptedData); } catch (Exception e) { - return ResponseEntity.status(500).body("File download failed: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("File download failed: " + e.getMessage()); } } diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java b/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java index 928d0e2..0d825b7 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java @@ -3,10 +3,16 @@ package com.skycrate.backend.skycrateBackend.repository; import com.skycrate.backend.skycrateBackend.entity.RefreshToken; import com.skycrate.backend.skycrateBackend.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; public interface RefreshTokenRepository extends JpaRepository { Optional findByToken(String token); + @Transactional + @Modifying + @Query("DELETE FROM RefreshToken t WHERE t.user = :user") void deleteByUser(User user); } \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java b/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java index 1a5a39b..9001d86 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java @@ -1,10 +1,10 @@ package com.skycrate.backend.skycrateBackend.services; import com.skycrate.backend.skycrateBackend.entity.User; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; @@ -15,13 +15,17 @@ import java.util.function.Function; @Service public class JwtService { - // Recommend moving this key to environment variable or config file - private static final String SECRET_KEY = "your-256-bit-secret-your-256-bit-secret"; // must be 256-bit + @Value("${security.jwt.secret-key}") + private String secretKey; - private final long EXPIRATION_TIME = 1000 * 60 * 15; // 15 minutes + @Value("${security.jwt.expiration-time}") + private long expirationTime; + + private static final String SECRET_KEY = "PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs="; private Key getSigningKey() { - return Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); + byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY); + return Keys.hmacShaKeyFor(keyBytes); } public String extractUsername(String token) { @@ -54,12 +58,11 @@ public class JwtService { return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .setExpiration(new Date(System.currentTimeMillis() + expirationTime)) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } - // Overload for your entity public String generateToken(User user) { return generateToken((UserDetails) user); } diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java b/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java index e7f90f7..b008b91 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java @@ -5,6 +5,7 @@ import com.skycrate.backend.skycrateBackend.entity.User; import com.skycrate.backend.skycrateBackend.repository.RefreshTokenRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.Instant; import java.util.Optional; @@ -22,8 +23,10 @@ public class RefreshTokenService { this.refreshTokenRepo = refreshTokenRepo; } + @Transactional public RefreshToken createRefreshToken(User user) { - refreshTokenRepo.deleteByUser(user); // Allow only 1 active token per user + refreshTokenRepo.deleteByUser(user); + refreshTokenRepo.flush(); RefreshToken token = new RefreshToken(); token.setUser(user); @@ -40,8 +43,8 @@ public class RefreshTokenService { return token.getExpiryDate().isBefore(Instant.now()); } + @Transactional public void deleteByUser(User user) { refreshTokenRepo.deleteByUser(user); } - } \ No newline at end of file