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
This commit is contained in:
K
2025-07-03 16:59:29 +05:30
parent 3920ec7fbd
commit c5ff741f8c
5 changed files with 27 additions and 13 deletions
@@ -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()
)
@@ -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());
}
}
@@ -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<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
@Transactional
@Modifying
@Query("DELETE FROM RefreshToken t WHERE t.user = :user")
void deleteByUser(User user);
}
@@ -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);
}
@@ -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);
}
}