From 7f6b2eb34436be99099d27522b0cd72ffb6e70e4 Mon Sep 17 00:00:00 2001 From: Kshitij <160704796+kshitij-ka@users.noreply.github.com> Date: Thu, 3 Jul 2025 04:48:29 +0530 Subject: [PATCH] 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}) --- pom.xml | 308 ++++++++---------- .../skycrateBackend/config/HDFSConfig.java | 13 +- .../config/HttpToHttpsRedirectConfig.java | 4 +- .../config/JwtAuthenticationFilter.java | 74 ----- .../config/SecurityConfig.java | 5 +- .../skycrateBackend/dto/RegisterUserDto.java | 74 ++--- .../skycrateBackend/entity/FileMetadata.java | 22 +- .../backend/skycrateBackend/entity/User.java | 52 +-- .../security/JwtAuthenticationFilter.java | 6 + .../security/RateLimiterService.java | 35 -- .../services/AuthenticationService.java | 34 +- 11 files changed, 240 insertions(+), 387 deletions(-) delete mode 100644 src/main/java/com/skycrate/backend/skycrateBackend/config/JwtAuthenticationFilter.java delete mode 100644 src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java diff --git a/pom.xml b/pom.xml index c664f71..c5855e0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,185 +1,153 @@ - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.4.4 - - - com.skycrate.backend - skycrateBackend - 0.0.1-SNAPSHOT - skycrateBackend - Cloud Storage App using HDFS - - - - - - - - - - - - - - - 17 - - - - org.springframework.boot - spring-boot-starter - + + 4.0.0 - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + + com.skycrate.backend + skycrateBackend + 0.0.1-SNAPSHOT + skycrateBackend + Cloud Storage App using HDFS - - - - org.springframework.boot - spring-boot-starter-web - 3.2.4 - + + 17 + - - - org.springframework.boot - spring-boot-starter-security - 3.2.4 - + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + - - - org.springframework.boot - spring-boot-starter-data-jpa - 3.2.4 - + + + com.mysql + mysql-connector-j + 8.3.0 + - - - com.mysql - mysql-connector-j - 8.3.0 - + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + - - - org.springframework.boot - spring-boot-starter-validation - 3.2.4 - + + + org.apache.hadoop + hadoop-common + 3.4.1 + + + org.slf4j + slf4j-log4j12 + + + + + org.apache.hadoop + hadoop-hdfs + 3.4.1 + + + org.apache.hadoop + hadoop-client + 3.4.1 + - - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - + + + commons-io + commons-io + 2.11.0 + - - - org.apache.hadoop - hadoop-common - 3.4.1 - - - org.slf4j - slf4j-log4j12 - - - + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + - - org.apache.hadoop - hadoop-hdfs - 3.4.1 - + + + org.projectlombok + lombok + 1.18.30 + provided + - - org.apache.hadoop - hadoop-client - 3.4.1 - + + + org.springframework.boot + spring-boot-starter-test + test + + - - - - - - - - - - - - - - - - commons-io - commons-io - 2.11.0 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - com.github.ben-manes.caffeine - caffeine - 3.1.8 - - - - - + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + 1.18.30 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java b/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java index 56f6b99..706490f 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java @@ -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."); diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/config/HttpToHttpsRedirectConfig.java b/src/main/java/com/skycrate/backend/skycrateBackend/config/HttpToHttpsRedirectConfig.java index c7f3184..aef8553 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/config/HttpToHttpsRedirectConfig.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/config/HttpToHttpsRedirectConfig.java @@ -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); }; } -} \ No newline at end of file +} diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/config/JwtAuthenticationFilter.java b/src/main/java/com/skycrate/backend/skycrateBackend/config/JwtAuthenticationFilter.java deleted file mode 100644 index 024b6c1..0000000 --- a/src/main/java/com/skycrate/backend/skycrateBackend/config/JwtAuthenticationFilter.java +++ /dev/null @@ -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); - } - - } - - -} 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 bdd4fc5..7cabb16 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/**").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); diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/dto/RegisterUserDto.java b/src/main/java/com/skycrate/backend/skycrateBackend/dto/RegisterUserDto.java index da001fd..484e35f 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/dto/RegisterUserDto.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/dto/RegisterUserDto.java @@ -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; } +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/entity/FileMetadata.java b/src/main/java/com/skycrate/backend/skycrateBackend/entity/FileMetadata.java index e74d582..a077276 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/entity/FileMetadata.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/entity/FileMetadata.java @@ -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; } + } \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java b/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java index bde3655..7138eaa 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java @@ -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 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; - } } \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/security/JwtAuthenticationFilter.java b/src/main/java/com/skycrate/backend/skycrateBackend/security/JwtAuthenticationFilter.java index 532743b..38b9e37 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/security/JwtAuthenticationFilter.java @@ -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; diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java b/src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java deleted file mode 100644 index 7aad7ee..0000000 --- a/src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java +++ /dev/null @@ -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 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); - } -} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/services/AuthenticationService.java b/src/main/java/com/skycrate/backend/skycrateBackend/services/AuthenticationService.java index ff333ba..7cea86d 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/services/AuthenticationService.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/services/AuthenticationService.java @@ -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); }