Refactor Auth and HDFS controllers, fix User model, and improve HDFS config

- Rewrote AuthController to inject all dependencies via constructor
- Fixed token refresh/login logic and added rate limiter and blacklist support
- Implemented getters in LoginRequest DTO
- Updated User model to implement UserDetails and extend entity.User
- Switched HDFScontroller to use entity.User instead of models.User
- Rewrote HDFSConfig to include static getHDFS() method and secure config via env vars
- Simplified JwtService, added overload for entity.User, and fixed key handling
This commit is contained in:
K
2025-07-03 03:47:08 +05:30
parent 9cb9c67b09
commit 12355f25c7
8 changed files with 225 additions and 108 deletions
@@ -8,13 +8,35 @@ import org.springframework.context.annotation.Bean;
import java.net.URI;
import java.security.PrivilegedExceptionAction;
// HDFS configuration bean to securely connect to a remote Hadoop cluster.
@Configuration
public class HDFSConfig {
public static FileSystem getHDFS() throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://namenode:9000");
return FileSystem.get(new URI("hdfs://namenode:9000"), conf);
private static final String HDFS_URI = System.getenv("HDFS_URI"); // export HDFS_URI=hdfs://192.168.29.30:9000
private static final String HDFS_USER = System.getenv("HDFS_USER"); // Hadoop user (if needed)
// Configures and returns a secured HDFS FileSystem instance.
@Bean
public FileSystem fileSystem() throws Exception {
return getHDFS(); // use the static method internally
}
// Static method to get a FileSystem instance. Used by other classes like HDFSController.
public static FileSystem getHDFS() throws Exception {
if (HDFS_URI == null || HDFS_URI.isBlank()) {
throw new IllegalStateException("HDFS_URI environment variable not set.");
}
}
Configuration conf = new Configuration();
conf.set("fs.defaultFS", HDFS_URI);
if (HDFS_USER != null && !HDFS_USER.isBlank()) {
return UserGroupInformation.createRemoteUser(HDFS_USER)
.doAs((PrivilegedExceptionAction<FileSystem>) () ->
FileSystem.get(new URI(HDFS_URI), conf)
);
} else {
return FileSystem.get(new URI(HDFS_URI), conf);
}
}
}
@@ -1,13 +1,17 @@
package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.dto.LoginRequest;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import com.skycrate.backend.skycrateBackend.dto.LoginResponse;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshRequest;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshResponse;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.security.TokenBlacklistService;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import com.skycrate.backend.skycrateBackend.services.RefreshTokenService;
import com.skycrate.backend.skycrateBackend.services.RateLimiterService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -20,16 +24,26 @@ public class AuthController {
private final AuthenticationManager authManager;
private final JwtService jwtService;
private final UserRepository userRepository;
private final RefreshTokenService refreshTokenService;
private final TokenBlacklistService tokenBlacklistService;
private final RateLimiterService rateLimiterService;
public AuthController(AuthenticationManager authManager, JwtService jwtService, UserRepository userRepository) {
public AuthController(
AuthenticationManager authManager,
JwtService jwtService,
UserRepository userRepository,
RefreshTokenService refreshTokenService,
TokenBlacklistService tokenBlacklistService,
RateLimiterService rateLimiterService
) {
this.authManager = authManager;
this.jwtService = jwtService;
this.userRepository = userRepository;
this.refreshTokenService = refreshTokenService;
this.tokenBlacklistService = tokenBlacklistService;
this.rateLimiterService = rateLimiterService;
}
@Autowired
private RefreshTokenService refreshTokenService;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) {
String ip = servletRequest.getRemoteAddr();
@@ -52,43 +66,29 @@ public class AuthController {
rateLimiterService.resetAttempts(ip);
// ✅ Generate tokens
String accessToken = jwtService.generateToken(user);
RefreshToken refreshToken = refreshTokenService.createRefreshToken(user);
return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken.getToken()));
}
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return ResponseEntity.badRequest().body("Missing or invalid Authorization header");
}
rateLimiterService.resetAttempts(ip);
String token = jwtService.generateToken(user);
return ResponseEntity.ok().body(token);
String token = authHeader.substring(7);
tokenBlacklistService.blacklistToken(token);
String email = jwtService.extractUsername(token);
userRepository.findByEmail(email).ifPresent(refreshTokenService::deleteByUser);
return ResponseEntity.ok("Logged out successfully");
}
@Autowired
private TokenBlacklistService tokenBlacklistService;
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return ResponseEntity.badRequest().body("Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
// Blacklist access token
tokenBlacklistService.blacklistToken(token);
// Extract user from token and delete their refresh token
String email = jwtService.extractUsername(token);
userRepository.findByEmail(email).ifPresent(refreshTokenService::deleteByUser);
return ResponseEntity.ok("Logged out successfully");
}
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody TokenRefreshRequest request) {
String requestToken = request.getRefreshToken();
@@ -105,5 +105,4 @@ public ResponseEntity<?> logout(HttpServletRequest request) {
})
.orElseGet(() -> ResponseEntity.status(403).body("Invalid refresh token"));
}
}
@@ -2,9 +2,8 @@ package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.dto.ResponseDTO;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.services.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.services.HDFSOperations;
import com.skycrate.backend.skycrateBackend.utils.KeyUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
@@ -36,9 +35,6 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File; // For java.io.File
import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPrivateKeyForUser;
import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPublicKeyForUser;
@RestController
@RequestMapping("/api/hdfs")
public class HDFScontroller {
@@ -5,4 +5,9 @@ public class LoginRequest {
private String password;
// Getters and setters
}
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
@@ -0,0 +1,90 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String fullname;
@Lob
private byte[] publicKey;
@Lob
private byte[] privateKey;
// --- UserDetails interface methods ---
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(); // Add roles/authorities if needed
}
@Override
public String getUsername() {
return email; // or return username if that's your login key
}
@Override
public String getPassword() {
return this.password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// --- Extra getter methods for HDFScontroller compatibility ---
public byte[] getPublicKey() {
return publicKey;
}
public byte[] getPrivateKey() {
return privateKey;
}
public String getFullname() {
return fullname;
}
}
@@ -1,6 +1,5 @@
package com.skycrate.backend.skycrateBackend.models;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@@ -13,7 +12,7 @@ import jakarta.persistence.*;
@Table(name = "users")
@Entity
public class User implements UserDetails {
public class User extends com.skycrate.backend.skycrateBackend.entity.User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1,83 +1,66 @@
package com.skycrate.backend.skycrateBackend.services;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import com.skycrate.backend.skycrateBackend.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.function.Function;
@Service
public class JwtService {
@Value("${security.jwt.secret-key}")
private String secretKey;
@Value("${security.jwt.expiration-time}")
private long jwtExpiration;
public String extractUsername(String token){
return extractClaim(token,Claims::getSubject);
// 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
private final long EXPIRATION_TIME = 1000 * 60 * 15; // 15 minutes
private Key getSigningKey() {
return Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
}
public <T> T extractClaim(String token,Function<Claims,T> claimsResolver){
final Claims claims=extractAllClaims(token);
return claimsResolver.apply(claims);
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return buildToken(extraClaims, userDetails, jwtExpiration);
}
public long getExpirtationTime(){
return jwtExpiration;
}
private String buildToken(Map<String,Object> extraClaims,UserDetails userDetails,long expiration){
return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claimsResolver.apply(claims);
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// Overload for your entity
public String generateToken(User user) {
return generateToken((UserDetails) user);
}
}
@@ -0,0 +1,23 @@
// NEED TO IMPLEMENT SAHI SE
package com.skycrate.backend.skycrateBackend.services;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class RateLimiterService {
private final ConcurrentHashMap<String, Integer> attempts = new ConcurrentHashMap<>();
public boolean isBlocked(String ip) {
return attempts.getOrDefault(ip, 0) >= 5;
}
public void recordFailedAttempt(String ip) {
attempts.put(ip, attempts.getOrDefault(ip, 0) + 1);
}
public void resetAttempts(String ip) {
attempts.remove(ip);
}
}