From 53e6a98ce4fd38e0bf822162d8ad35ed67b0b077 Mon Sep 17 00:00:00 2001 From: kenduNMT Date: Mon, 10 Nov 2025 17:01:44 +0700 Subject: [PATCH] login register --- .../config/security/SecurityConfig.java | 57 +++++++ .../exception/AccessDeniedHandler.java | 19 +++ .../exception/AuthenticationEntryPoint.java | 19 +++ .../security/jwt/JWTAuthTokenFilter.java | 142 ++++++++++++++++++ .../config/security/jwt/JWTProvider.java | 54 +++++++ .../security/principal/UserDetailsCus.java | 84 +++++++++++ .../principal/UserDetailsServiceCus.java | 29 ++++ .../controller/AuthController.java | 34 +++++ .../model/dto/request/AuthRequest.java | 11 ++ .../model/dto/response/AuthResponse.java | 15 ++ .../model/entity/User.java | 22 ++- .../repository/RoleRepository.java | 12 ++ .../repository/UserRepository.java | 5 +- .../service/AuthService.java | 79 ++++++++++ src/main/resources/application.properties | 1 + 15 files changed, 576 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/example/project_it207_server/config/security/SecurityConfig.java create mode 100644 src/main/java/com/example/project_it207_server/config/security/exception/AccessDeniedHandler.java create mode 100644 src/main/java/com/example/project_it207_server/config/security/exception/AuthenticationEntryPoint.java create mode 100644 src/main/java/com/example/project_it207_server/config/security/jwt/JWTAuthTokenFilter.java create mode 100644 src/main/java/com/example/project_it207_server/config/security/jwt/JWTProvider.java create mode 100644 src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsCus.java create mode 100644 src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsServiceCus.java create mode 100644 src/main/java/com/example/project_it207_server/controller/AuthController.java create mode 100644 src/main/java/com/example/project_it207_server/model/dto/request/AuthRequest.java create mode 100644 src/main/java/com/example/project_it207_server/model/dto/response/AuthResponse.java create mode 100644 src/main/java/com/example/project_it207_server/repository/RoleRepository.java create mode 100644 src/main/java/com/example/project_it207_server/service/AuthService.java diff --git a/src/main/java/com/example/project_it207_server/config/security/SecurityConfig.java b/src/main/java/com/example/project_it207_server/config/security/SecurityConfig.java new file mode 100644 index 0000000..6352662 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/SecurityConfig.java @@ -0,0 +1,57 @@ +package com.example.project_it207_server.config.security; + +import com.example.project_it207_server.config.security.jwt.JWTAuthTokenFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableMethodSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JWTAuthTokenFilter jwtAuthTokenFilter; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + // ✅ Cho phép truy cập các API public + .requestMatchers( + "/api/auth/register", + "/api/auth/login", + "/api/auth/forgot-password", + "/api/auth/confirm-password-change", + "/public/**" + ).permitAll() + // ✅ Tất cả API khác yêu cầu xác thực + .anyRequest().authenticated() + ); + + // ✅ Thêm JWT filter trước UsernamePasswordAuthenticationFilter + http.addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } +} diff --git a/src/main/java/com/example/project_it207_server/config/security/exception/AccessDeniedHandler.java b/src/main/java/com/example/project_it207_server/config/security/exception/AccessDeniedHandler.java new file mode 100644 index 0000000..281226c --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/exception/AccessDeniedHandler.java @@ -0,0 +1,19 @@ +package com.example.project_it207_server.config.security.exception; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; + +import java.io.IOException; + +public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"" + accessDeniedException.getMessage() + "\"}"); + response.getWriter().flush(); + response.getWriter().close(); + } +} diff --git a/src/main/java/com/example/project_it207_server/config/security/exception/AuthenticationEntryPoint.java b/src/main/java/com/example/project_it207_server/config/security/exception/AuthenticationEntryPoint.java new file mode 100644 index 0000000..62377d8 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/exception/AuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package com.example.project_it207_server.config.security.exception; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; + +import java.io.IOException; + +public class AuthenticationEntryPoint implements org.springframework.security.web.AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}"); + response.getWriter().flush(); + response.getWriter().close(); + } +} diff --git a/src/main/java/com/example/project_it207_server/config/security/jwt/JWTAuthTokenFilter.java b/src/main/java/com/example/project_it207_server/config/security/jwt/JWTAuthTokenFilter.java new file mode 100644 index 0000000..09e5c61 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/jwt/JWTAuthTokenFilter.java @@ -0,0 +1,142 @@ +package com.example.project_it207_server.config.security.jwt; + +import com.example.project_it207_server.redis.RedisService; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +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 java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * JWT Authentication Filter với kiểm tra Redis + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class JWTAuthTokenFilter extends OncePerRequestFilter { + + private final JWTProvider jwtProvider; + private final UserDetailsService userDetailsService; + private final RedisService redisService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + try { + String token = getTokenFromRequest(request); + + if (token != null) { + // Kiểm tra token hợp lệ + if (jwtProvider.validateToken(token)) { + String username = jwtProvider.getUserNameFromToken(token); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + + // Kiểm tra token có tồn tại trong Redis không + String redisKey = "token:" + username + ":" + token; + if (!redisService.hasKey(redisKey)) { + log.warn("Token không tồn tại trong Redis cho user: {}", username); + handleAuthenticationError(response, "Token đã hết hạn hoặc không hợp lệ"); + return; + } + + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + if (userDetails != null) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } + } + + filterChain.doFilter(request, response); + + } catch (Exception e) { + log.error("Lỗi trong quá trình xác thực: ", e); + handleAuthenticationError(response, e.getMessage()); + } + } + + /** + * Lấy JWT token từ request header + */ + private String getTokenFromRequest(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + + return null; + } + + /** + * Xử lý khi token bị blacklist + */ + private void handleBlacklistedToken(HttpServletResponse response) throws IOException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("code", 401); + errorResponse.put("message", "Token đã bị vô hiệu hóa"); + errorResponse.put("details", Map.of("error", "Token đã được đăng xuất")); + + String jsonResponse = objectMapper.writeValueAsString(errorResponse); + response.getWriter().write(jsonResponse); + } + + /** + * Xử lý lỗi authentication + */ + private void handleAuthenticationError(HttpServletResponse response, String errorMessage) throws IOException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("code", 401); + errorResponse.put("message", "Xác thực thất bại"); + errorResponse.put("details", Map.of("error", errorMessage)); + + String jsonResponse = objectMapper.writeValueAsString(errorResponse); + response.getWriter().write(jsonResponse); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getRequestURI(); + + // Không filter các endpoint public + return path.startsWith("/api/auth/login") || + path.startsWith("/api/auth/register") || + path.startsWith("/api/auth/oauth2/firebase") || + path.startsWith("/api/auth/forgot-password") || + path.startsWith("/api/auth/confirm-password-change") || + path.startsWith("/public/"); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/project_it207_server/config/security/jwt/JWTProvider.java b/src/main/java/com/example/project_it207_server/config/security/jwt/JWTProvider.java new file mode 100644 index 0000000..395ac27 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/jwt/JWTProvider.java @@ -0,0 +1,54 @@ +package com.example.project_it207_server.config.security.jwt; + +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.util.Date; +@Slf4j +@Service +public class JWTProvider { + + @Value("${jwt.secret-key}") + private String secretKey; + + @Value("${jwt.expiration}") + private long jwtExpiration; + + + + + public String generateToken(UserDetails userDetails) { + Date today = new Date(); + return Jwts.builder() + .setSubject(userDetails.getUsername()) + .setIssuedAt(today) + .setExpiration(new Date(today.getTime() + jwtExpiration)) + .signWith( SignatureAlgorithm.HS512, secretKey) + .compact(); + } + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + log.error("JWT: message error expired:", e.getMessage()); + } catch (UnsupportedJwtException e) { + log.error("JWT: message error unsupported:", e.getMessage()); + } catch (MalformedJwtException e) { + log.error("JWT: message error not formated:", e.getMessage()); + } catch (SignatureException e) { + log.error("JWT: message error signature not math:", e.getMessage()); + } catch (IllegalArgumentException e) { + log.error("JWT: message claims empty or argument invalid: ", e.getMessage()); + } + return false; + } + // Giai mã token + public String getUserNameFromToken(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsCus.java b/src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsCus.java new file mode 100644 index 0000000..498176c --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsCus.java @@ -0,0 +1,84 @@ +package com.example.project_it207_server.config.security.principal; + +import com.example.project_it207_server.model.entity.Role; +import com.example.project_it207_server.model.entity.User; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +/** + * Lớp ánh xạ thông tin người dùng sang dạng UserDetails để Spring Security sử dụng + */ +@Getter +@Builder +@AllArgsConstructor +public class UserDetailsCus implements UserDetails { + + private Long id; + private String username; + private String email; + private String password; + private Role role; + private Collection authorities; + private boolean enabled; + + /** + * Xây dựng UserDetailsCus từ entity User + */ + public static UserDetailsCus build(User user) { + // ✅ Lấy quyền từ tên role (VD: "USER", "ADMIN") + SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().getRoleName()); + + return UserDetailsCus.builder() + .id(user.getUserId()) + .username(user.getUsername()) + .email(user.getEmail()) + .password(user.getPassword()) + .role(user.getRole()) + .authorities(Collections.singletonList(authority)) + .enabled(true) + .build(); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + // Dùng email hoặc username đều được + return email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return enabled; + } +} diff --git a/src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsServiceCus.java b/src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsServiceCus.java new file mode 100644 index 0000000..d0adcbc --- /dev/null +++ b/src/main/java/com/example/project_it207_server/config/security/principal/UserDetailsServiceCus.java @@ -0,0 +1,29 @@ +package com.example.project_it207_server.config.security.principal; + +import com.example.project_it207_server.model.entity.User; +import com.example.project_it207_server.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * Lấy thông tin người dùng từ database để Spring Security xác thực + */ +@Service +@RequiredArgsConstructor +public class UserDetailsServiceCus implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException { + // ✅ Cho phép đăng nhập bằng email hoặc username + User user = userRepository.findByUsername(usernameOrEmail) + .or(() -> userRepository.findByEmail(usernameOrEmail)) + .orElseThrow(() -> new UsernameNotFoundException("Không tìm thấy tài khoản: " + usernameOrEmail)); + + return UserDetailsCus.build(user); + } +} diff --git a/src/main/java/com/example/project_it207_server/controller/AuthController.java b/src/main/java/com/example/project_it207_server/controller/AuthController.java new file mode 100644 index 0000000..fcecf8d --- /dev/null +++ b/src/main/java/com/example/project_it207_server/controller/AuthController.java @@ -0,0 +1,34 @@ +package com.example.project_it207_server.controller; + +import com.example.project_it207_server.model.dto.request.AuthRequest; +import com.example.project_it207_server.model.dto.response.AuthResponse; +import com.example.project_it207_server.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +public class AuthController { + + private final AuthService authService; + + @PostMapping("/register") + public ResponseEntity register(@RequestBody AuthRequest request) { + return ResponseEntity.ok(authService.register(request)); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody AuthRequest request) { + return ResponseEntity.ok(authService.login(request)); + } + + @PostMapping("/logout") + public ResponseEntity logout(@RequestHeader("Authorization") String authHeader) { + String token = authHeader.replace("Bearer ", ""); + authService.logout(token); + return ResponseEntity.ok().body("Đăng xuất thành công"); + } +} diff --git a/src/main/java/com/example/project_it207_server/model/dto/request/AuthRequest.java b/src/main/java/com/example/project_it207_server/model/dto/request/AuthRequest.java new file mode 100644 index 0000000..cbb6d08 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/model/dto/request/AuthRequest.java @@ -0,0 +1,11 @@ +package com.example.project_it207_server.model.dto.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AuthRequest { + private String email; + private String password; +} \ No newline at end of file diff --git a/src/main/java/com/example/project_it207_server/model/dto/response/AuthResponse.java b/src/main/java/com/example/project_it207_server/model/dto/response/AuthResponse.java new file mode 100644 index 0000000..e223d42 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/model/dto/response/AuthResponse.java @@ -0,0 +1,15 @@ +package com.example.project_it207_server.model.dto.response; + +import com.example.project_it207_server.model.entity.Role; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class AuthResponse { + private String token; + private String email; + private Role role; +} \ No newline at end of file diff --git a/src/main/java/com/example/project_it207_server/model/entity/User.java b/src/main/java/com/example/project_it207_server/model/entity/User.java index 7cee5f7..48c889b 100644 --- a/src/main/java/com/example/project_it207_server/model/entity/User.java +++ b/src/main/java/com/example/project_it207_server/model/entity/User.java @@ -6,7 +6,11 @@ import java.time.LocalDateTime; @Entity @Table(name = "users") -@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class User { @Id @@ -16,12 +20,12 @@ public class User { @Column(unique = true, nullable = false) private String username; - @Column(nullable = false) - private String passwordHash; - @Column(unique = true, nullable = false) private String email; + @Column(nullable = false) + private String password; + private String firstName; private String lastName; private String gender; @@ -29,10 +33,16 @@ public class User { private String address; private String avatarUrl; - @ManyToOne + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "role_id") private Role role; private LocalDateTime createdAt = LocalDateTime.now(); private LocalDateTime updatedAt = LocalDateTime.now(); -} + + // ✅ Tự động cập nhật thời gian khi update entity + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/project_it207_server/repository/RoleRepository.java b/src/main/java/com/example/project_it207_server/repository/RoleRepository.java new file mode 100644 index 0000000..618be7d --- /dev/null +++ b/src/main/java/com/example/project_it207_server/repository/RoleRepository.java @@ -0,0 +1,12 @@ +package com.example.project_it207_server.repository; + +import com.example.project_it207_server.model.entity.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RoleRepository extends JpaRepository { + Optional findByRoleName(String roleName); +} diff --git a/src/main/java/com/example/project_it207_server/repository/UserRepository.java b/src/main/java/com/example/project_it207_server/repository/UserRepository.java index 6d02e12..3f9391a 100644 --- a/src/main/java/com/example/project_it207_server/repository/UserRepository.java +++ b/src/main/java/com/example/project_it207_server/repository/UserRepository.java @@ -2,8 +2,11 @@ package com.example.project_it207_server.repository; import com.example.project_it207_server.model.entity.User; import org.springframework.data.jpa.repository.JpaRepository; + import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); -} \ No newline at end of file + Optional findByEmail(String email); + boolean existsByEmail(String email); +} diff --git a/src/main/java/com/example/project_it207_server/service/AuthService.java b/src/main/java/com/example/project_it207_server/service/AuthService.java new file mode 100644 index 0000000..8268317 --- /dev/null +++ b/src/main/java/com/example/project_it207_server/service/AuthService.java @@ -0,0 +1,79 @@ +package com.example.project_it207_server.service; + +import com.example.project_it207_server.config.security.jwt.JWTProvider; +import com.example.project_it207_server.model.dto.request.AuthRequest; +import com.example.project_it207_server.model.dto.response.AuthResponse; +import com.example.project_it207_server.model.entity.Role; +import com.example.project_it207_server.model.entity.User; +import com.example.project_it207_server.redis.RedisService; +import com.example.project_it207_server.repository.RoleRepository; +import com.example.project_it207_server.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final UserRepository userRepository; + private final RoleRepository roleRepository; + private final JWTProvider jwtProvider; + private final RedisService redisService; + private final UserDetailsService userDetailsService; + private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + public AuthResponse register(AuthRequest req) { + if (userRepository.existsByEmail(req.getEmail())) { + throw new RuntimeException("Email đã tồn tại!"); + } + + // 🔹 Lấy role mặc định (nếu chưa có, tự tạo) + Role defaultRole = roleRepository.findByRoleName("USER") + .orElseGet(() -> roleRepository.save(Role.builder().roleName("USER").build())); + + User newUser = User.builder() + .username(req.getEmail()) + .email(req.getEmail()) + .password(passwordEncoder.encode(req.getPassword())) + .role(defaultRole) + .build(); + + userRepository.save(newUser); + + UserDetails userDetails = userDetailsService.loadUserByUsername(newUser.getEmail()); + String token = jwtProvider.generateToken(userDetails); + + String redisKey = "token:" + newUser.getEmail() + ":" + token; + redisService.setValueWithExpiry(redisKey, token, 1, TimeUnit.DAYS);// TTL 1 ngày + + return new AuthResponse(token, newUser.getEmail(), newUser.getRole()); + } + + public AuthResponse login(AuthRequest req) { + User user = userRepository.findByEmail(req.getEmail()) + .orElseThrow(() -> new RuntimeException("Tài khoản không tồn tại")); + + if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) { + throw new RuntimeException("Sai mật khẩu"); + } + + UserDetails userDetails = userDetailsService.loadUserByUsername(user.getEmail()); + String token = jwtProvider.generateToken(userDetails); + + String redisKey = "token:" + user.getEmail() + ":" + token; + redisService.setValueWithExpiry(redisKey, token, 86400L, TimeUnit.SECONDS); + + return new AuthResponse(token, user.getEmail(), user.getRole()); + } + + public void logout(String token) { + String username = jwtProvider.getUserNameFromToken(token); + String redisKey = "token:" + username + ":" + token; + redisService.deleteKey(redisKey); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f1c4bb0..0385e92 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,7 @@ spring.application.name=project_it207_server server.port=8080 +server.address=0.0.0.0 spring.datasource.url=jdbc:mysql://localhost:3306/project_it207?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password=