Add refresh token support with /api/auth/refresh endpoint
- RefreshToken entity added with 1-token-per-user logic. - JWT can be renewed without full login using refresh token.
This commit is contained in:
@@ -66,4 +66,25 @@ public class AuthController {
|
|||||||
|
|
||||||
return ResponseEntity.ok("Logged out successfully");
|
return ResponseEntity.ok("Logged out successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RefreshTokenService refreshTokenService;
|
||||||
|
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
public ResponseEntity<?> refresh(@RequestBody TokenRefreshRequest request) {
|
||||||
|
String requestToken = request.getRefreshToken();
|
||||||
|
|
||||||
|
return refreshTokenService.findByToken(requestToken)
|
||||||
|
.map(token -> {
|
||||||
|
if (refreshTokenService.isExpired(token)) {
|
||||||
|
return ResponseEntity.status(403).body("Refresh token expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = token.getUser();
|
||||||
|
String newAccessToken = jwtService.generateToken(user);
|
||||||
|
return ResponseEntity.ok(new TokenRefreshResponse(newAccessToken, requestToken));
|
||||||
|
})
|
||||||
|
.orElseGet(() -> ResponseEntity.status(403).body("Invalid refresh token"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.skycrate.backend.skycrateBackend.dto;
|
||||||
|
|
||||||
|
public class TokenRefreshRequest {
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
public String getRefreshToken() {
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshToken(String refreshToken) {
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.skycrate.backend.skycrateBackend.dto;
|
||||||
|
|
||||||
|
public class TokenRefreshResponse {
|
||||||
|
private String accessToken;
|
||||||
|
private String refreshToken;
|
||||||
|
private String tokenType = "Bearer";
|
||||||
|
|
||||||
|
public TokenRefreshResponse(String accessToken, String refreshToken) {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
public String getAccessToken() { return accessToken; }
|
||||||
|
public String getRefreshToken() { return refreshToken; }
|
||||||
|
public String getTokenType() { return tokenType; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.skycrate.backend.skycrateBackend.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class RefreshToken {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@OneToOne
|
||||||
|
@JoinColumn(name = "user_id", referencedColumnName = "id")
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
private Instant expiryDate;
|
||||||
|
|
||||||
|
// Getters and setters
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
|
||||||
|
public String getToken() { return token; }
|
||||||
|
public void setToken(String token) { this.token = token; }
|
||||||
|
|
||||||
|
public User getUser() { return user; }
|
||||||
|
public void setUser(User user) { this.user = user; }
|
||||||
|
|
||||||
|
public Instant getExpiryDate() { return expiryDate; }
|
||||||
|
public void setExpiryDate(Instant expiryDate) { this.expiryDate = expiryDate; }
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
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 java.util.Optional;
|
||||||
|
|
||||||
|
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
|
||||||
|
Optional<RefreshToken> findByToken(String token);
|
||||||
|
void deleteByUser(User user);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.skycrate.backend.skycrateBackend.services;
|
||||||
|
|
||||||
|
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
|
||||||
|
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 java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class RefreshTokenService {
|
||||||
|
|
||||||
|
private final RefreshTokenRepository refreshTokenRepo;
|
||||||
|
|
||||||
|
@Value("${security.jwt.refresh-expiry-ms:604800000}") // 7 days default
|
||||||
|
private Long refreshTokenDurationMs;
|
||||||
|
|
||||||
|
public RefreshTokenService(RefreshTokenRepository refreshTokenRepo) {
|
||||||
|
this.refreshTokenRepo = refreshTokenRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RefreshToken createRefreshToken(User user) {
|
||||||
|
refreshTokenRepo.deleteByUser(user); // Allow only 1 active token per user
|
||||||
|
|
||||||
|
RefreshToken token = new RefreshToken();
|
||||||
|
token.setUser(user);
|
||||||
|
token.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
|
||||||
|
token.setToken(UUID.randomUUID().toString());
|
||||||
|
return refreshTokenRepo.save(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<RefreshToken> findByToken(String token) {
|
||||||
|
return refreshTokenRepo.findByToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired(RefreshToken token) {
|
||||||
|
return token.getExpiryDate().isBefore(Instant.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user