Refactor and secure backend configuration, DTOs, and authentication flow

- Updated pom.xml: removed redundant tags, grouped dependencies, added scopes, and upgraded plugins
- Enhanced RegisterUserDto with validation annotations and added missing fields (username, fullname)
- Updated User entity with builder constructor and removed redundant getters/setters
- Completed FileMetadata entity with Lombok and required setters/getters
- Improved HDFSConfig with correct annotation and clearer exception message
- Adjusted HTTP to HTTPS redirect port (8085 -> 8443)
- Allowed /actuator/** in SecurityConfig and disabled deprecated XSS protection
- Skipped JWT filter for /api/auth and /actuator paths
- Refactored AuthenticationService to use builder pattern and RSA key injection
- Fixed application.properties for static MySQL connection (removed ${MYSQL_PASSWORD})
This commit is contained in:
K
2025-07-03 04:48:29 +05:30
parent 88fd49c807
commit 7f6b2eb344
11 changed files with 240 additions and 387 deletions
@@ -1,6 +1,6 @@
package com.skycrate.backend.skycrateBackend.config;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configuration; // Hadoop Configuration
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.security.UserGroupInformation;
import org.springframework.context.annotation.Bean;
@@ -8,20 +8,17 @@ 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
@org.springframework.context.annotation.Configuration
public class HDFSConfig {
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)
private static final String HDFS_URI = System.getenv("HDFS_URI"); // e.g., hdfs://namenode:9000
private static final String HDFS_USER = System.getenv("HDFS_USER"); // e.g., hdfsuser
// Configures and returns a secured HDFS FileSystem instance.
@Bean
public FileSystem fileSystem() throws Exception {
return getHDFS(); // use the static method internally
return getHDFS();
}
// 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.");
@@ -14,10 +14,10 @@ public class HttpToHttpsRedirectConfig {
return factory -> {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080); // HTTP port
connector.setPort(8085); // HTTP port
connector.setSecure(false);
connector.setRedirectPort(8443); // HTTPS port
factory.addAdditionalTomcatConnectors(connector);
};
}
}
}
@@ -1,74 +0,0 @@
package com.skycrate.backend.skycrateBackend.config;
import java.io.IOException;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final HandlerExceptionResolver handlerExceptionResolver;
private JwtService jwtService;
private UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtService jwtService,UserDetailsService userDetailsService,HandlerExceptionResolver handlerExceptionResolver){
this.handlerExceptionResolver=handlerExceptionResolver;
this.jwtService=jwtService;
this.userDetailsService=userDetailsService;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader=request.getHeader("Authorization");
if (authHeader==null || !authHeader.startsWith("Bearer")){
filterChain.doFilter(request, response);
return;
}
try {
final String userjwt=authHeader.substring(7);
final String userEmail=jwtService.extractUsername(userjwt);
Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
if(userEmail!=null && authentication==null){
UserDetails userDetails=this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(userjwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
catch (Exception err) {
handlerExceptionResolver.resolveException(request, response, null, err);
}
}
}
@@ -29,7 +29,7 @@ public class SecurityConfig {
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/auth/**", "/actuator/**").permitAll()
.requestMatchers(HttpMethod.GET, "/public/**").permitAll()
.anyRequest().authenticated()
)
@@ -41,7 +41,8 @@ public class SecurityConfig {
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
.xssProtection(xss -> xss.block(true))
// Spring Security 6+ no longer supports xss.block(true), so we just enable or disable it.
.xssProtection(xss -> xss.disable())
.frameOptions(frame -> frame.deny())
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
@@ -1,46 +1,46 @@
package com.skycrate.backend.skycrateBackend.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class RegisterUserDto {
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
@NotBlank(message = "Username is required")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "Username must be alphanumeric")
private String username;
@NotBlank(message = "Full name is required")
private String fullname;
@NotBlank(message = "First name is required")
private String firstname;
@NotBlank(message = "Last name is required")
private String lastname;
public String getEmail() {
return email;
}
// Getters
public String getEmail() { return email; }
public String getPassword() { return password; }
public String getUsername() { return username; }
public String getFullname() { return fullname; }
public String getFirstname() { return firstname; }
public String getLastname() { return lastname; }
public RegisterUserDto setEmail(String email) {
this.email = email;
return this;
}
public String getPassword() {
return password;
}
public RegisterUserDto setPassword(String password) {
this.password = password;
return this;
}
public String getFirstname() {
return firstname;
}
public RegisterUserDto setFirstname(String firstname) {
this.firstname = firstname;
return this;
}
public String getLastname() {
return lastname;
}
public RegisterUserDto setLastname(String lastname) {
this.lastname = lastname;
return this;
}
}
// Setters
public void setEmail(String email) { this.email = email; }
public void setPassword(String password) { this.password = password; }
public void setUsername(String username) { this.username = username; }
public void setFullname(String fullname) { this.fullname = fullname; }
public void setFirstname(String firstname) { this.firstname = firstname; }
public void setLastname(String lastname) { this.lastname = lastname; }
}
@@ -1,23 +1,43 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "file_metadata")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileMetadata {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String filePath;
@Column(nullable = false)
private String username;
@Lob
@Column(nullable = false)
private byte[] salt;
@Lob
@Column(nullable = false)
private byte[] iv;
// Getters and Setters
public void setUsername(String username) { this.username = username; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public void setSalt(byte[] salt) { this.salt = salt; }
public void setIv(byte[] iv) { this.iv = iv; }
public String getUsername() { return this.username; }
public String getFilePath() { return this.filePath; }
public byte[] getSalt() { return this.salt; }
public byte[] getIv() { return this.iv; }
}
@@ -14,7 +14,6 @@ import java.util.List;
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User implements UserDetails {
@Id
@@ -39,6 +38,16 @@ public class User implements UserDetails {
@Lob
private byte[] privateKey;
@Builder
public User(String email, String password, String username, String fullname, byte[] publicKey, byte[] privateKey) {
this.email = email;
this.password = password;
this.username = username;
this.fullname = fullname;
this.publicKey = publicKey;
this.privateKey = privateKey;
}
// --- UserDetails interface methods ---
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
@@ -74,45 +83,4 @@ public class User implements UserDetails {
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;
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
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;
}
}
@@ -37,6 +37,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
if (path.startsWith("/api/auth") || path.startsWith("/actuator")) {
filterChain.doFilter(request, response);
return;
}
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
@@ -1,35 +0,0 @@
package com.skycrate.backend.skycrateBackend.security;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RateLimiterService {
private final Cache<String, Integer> attemptsCache;
private static final int MAX_ATTEMPTS = 5;
public RateLimiterService() {
this.attemptsCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
}
public boolean isBlocked(String key) {
Integer attempts = attemptsCache.getIfPresent(key);
return attempts != null && attempts >= MAX_ATTEMPTS;
}
public void recordFailedAttempt(String key) {
int attempts = attemptsCache.getIfPresent(key) == null ? 0 : attemptsCache.getIfPresent(key);
attemptsCache.put(key, attempts + 1);
}
public void resetAttempts(String key) {
attemptsCache.invalidate(key);
}
}
@@ -1,15 +1,14 @@
package com.skycrate.backend.skycrateBackend.services;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
@@ -21,28 +20,31 @@ public class AuthenticationService {
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
public AuthenticationService(UserRepository userRepository, AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder) {
public AuthenticationService(UserRepository userRepository,
AuthenticationManager authenticationManager,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
}
public User signUp(RegisterUserDto inputUser) {
User user = new User(
inputUser.getFirstname(),
inputUser.getLastname(),
inputUser.getEmail(),
passwordEncoder.encode(inputUser.getPassword())
);
KeyPair keyPair;
try {
KeyPair keyPair = RSAKeyUtil.generateKeyPair();
user.setPublicKey(keyPair.getPublic().getEncoded());
user.setPrivateKey(keyPair.getPrivate().getEncoded());
keyPair = RSAKeyUtil.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to generate RSA key pair", e);
}
User user = User.builder()
.fullname(inputUser.getFirstname() + " " + inputUser.getLastname())
.username(inputUser.getUsername())
.email(inputUser.getEmail())
.password(passwordEncoder.encode(inputUser.getPassword()))
.publicKey(keyPair.getPublic().getEncoded())
.privateKey(keyPair.getPrivate().getEncoded())
.build();
return userRepository.save(user);
}