diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index 71ac2b0..317b070 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -33,8 +33,9 @@ public BaseResponse logout(HttpServletRequest request, } @PostMapping("/reissue") - public BaseResponse reissueTokens(@RequestBody RefreshTokenRequest tokenRequest) { - ReissueResponse response = jwtService.reissueTokens(tokenRequest); + public BaseResponse reissueTokens(@RequestBody RefreshTokenRequest tokenRequest, + @CurrentUserId Long userId) { + ReissueResponse response = jwtService.reissueTokens(tokenRequest, userId); return BaseResponse.ok(response); } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java index e1ff12d..b826084 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java @@ -2,6 +2,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomJwtException; +import com.WhoIsRoom.WhoIs_Server.domain.auth.handler.exception.CustomAuthenticationEntryPoint; import com.WhoIsRoom.WhoIs_Server.domain.auth.model.UserPrincipal; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; import com.WhoIsRoom.WhoIs_Server.domain.auth.util.JwtUtil; @@ -14,6 +15,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -37,10 +39,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final JwtService jwtService; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; // 인증을 안해도 되니 토큰이 필요없는 URL들 (에러: 로그인이 필요합니다) public final static List PASS_URIS = Arrays.asList( - "/api/users/signup", "/api/auth/**" + "/api/users/signup", "/api/auth/login", + "/api/auth/email/send", "/api/auth/email/validation" ); private static final AntPathMatcher ANT = new AntPathMatcher(); @@ -48,60 +52,64 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - if(isPassUri(request.getRequestURI())) { - log.info("JWT Filter Passed (pass uri) : {}", request.getRequestURI()); - filterChain.doFilter(request, response); - return; - } + try { - // 엑세스 토큰이 없으면 Authentication도 없음 -> EntryPoint (401) - log.info("Request URI: {}", request.getRequestURI()); // 요청 URI 로깅 - String accessToken = jwtUtil.extractAccessToken(request) - .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_UNAUTHORIZED)); + if (isPassUri(request.getRequestURI())) { + log.info("JWT Filter Passed (pass uri) : {}", request.getRequestURI()); + filterChain.doFilter(request, response); + return; + } - // 토큰 유효성 검사 - jwtUtil.validateToken(accessToken); + // 엑세스 토큰이 없으면 Authentication도 없음 -> EntryPoint (401) + log.info("Request URI: {}", request.getRequestURI()); // 요청 URI 로깅 + String accessToken = jwtUtil.extractAccessToken(request) + .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_UNAUTHORIZED)); - // 토큰 타입 검사 - if(!"access".equals(jwtUtil.getTokenType(accessToken))) { - throw new CustomJwtException(ErrorCode.INVALID_TOKEN_TYPE); - } + // 토큰 유효성 검사 + jwtUtil.validateToken(accessToken); - // 로그아웃 체크 - jwtService.checkLogout(accessToken); + // 토큰 타입 검사 + if (!"access".equals(jwtUtil.getTokenType(accessToken))) { + throw new CustomJwtException(ErrorCode.INVALID_TOKEN_TYPE); + } - // 권한 리스트 생성 - List authorities = Arrays.asList(new SimpleGrantedAuthority(jwtUtil.getRole(accessToken))); - log.info("Granted Authorities : {}", authorities); - UserPrincipal principal = new UserPrincipal( - jwtUtil.getUserId(accessToken), - jwtUtil.getName(accessToken), - null, // 패스워드는 필요 없음 - jwtUtil.getProviderId(accessToken), - authorities - ); - log.info("UserPrincipal.userId: {}", principal.getUserId()); - log.info("UserPrincipal.nickName: {}", principal.getUsername()); - log.info("UserPrincipal.providerId: {}", principal.getProviderId()); - log.info("UserPrincipal.role: {}", principal.getAuthorities().stream().findFirst().get().toString()); + // 로그아웃 체크 + jwtService.checkLogout(accessToken); - Authentication authToken = null; - if ("localhost".equals(principal.getProviderId())) { - // 폼 로그인(자체 회원) - authToken = new UsernamePasswordAuthenticationToken(principal, null, authorities); - } + // 권한 리스트 생성 + List authorities = Arrays.asList(new SimpleGrantedAuthority(jwtUtil.getRole(accessToken))); + log.info("Granted Authorities : {}", authorities); + UserPrincipal principal = new UserPrincipal( + jwtUtil.getUserId(accessToken), + jwtUtil.getName(accessToken), + null, // 패스워드는 필요 없음 + jwtUtil.getProviderId(accessToken), + authorities + ); + log.info("UserPrincipal.userId: {}", principal.getUserId()); + log.info("UserPrincipal.nickName: {}", principal.getUsername()); + log.info("UserPrincipal.providerId: {}", principal.getProviderId()); + log.info("UserPrincipal.role: {}", principal.getAuthorities().stream().findFirst().get().toString()); + + Authentication authToken = null; + if ("localhost".equals(principal.getProviderId())) { + // 폼 로그인(자체 회원) + authToken = new UsernamePasswordAuthenticationToken(principal, null, authorities); + } // else { // // 소셜 로그인 // authToken = new OAuth2AuthenticationToken(principal, authorities, loginProvider); // } - log.info("Authentication set in SecurityContext: {}", SecurityContextHolder.getContext().getAuthentication()); - log.info("Authorities in SecurityContext: {}", authToken.getAuthorities()); + log.info("Authentication set in SecurityContext: {}", SecurityContextHolder.getContext().getAuthentication()); + log.info("Authorities in SecurityContext: {}", authToken.getAuthorities()); - log.info("JWT Filter Success : {}", request.getRequestURI()); - SecurityContextHolder.getContext().setAuthentication(authToken); - filterChain.doFilter(request, response); + log.info("JWT Filter Success : {}", request.getRequestURI()); + SecurityContextHolder.getContext().setAuthentication(authToken); + filterChain.doFilter(request, response); + } catch (CustomAuthenticationException | AuthenticationException e) { + customAuthenticationEntryPoint.commence(request, response, (org.springframework.security.core.AuthenticationException) e); + } } - private boolean isPassUri(String uri) { return PASS_URIS.stream().anyMatch(pattern -> ANT.match(pattern, uri)); } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java index fe2be56..906c999 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java @@ -4,7 +4,6 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.util.AuthenticationUtil; import com.WhoIsRoom.WhoIs_Server.domain.auth.util.JwtUtil; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; -import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseErrorResponse; import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; @@ -35,7 +34,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String providerId = authenticationUtil.getProviderId(); String role = authenticationUtil.getRole(); - Long memberId = authenticationUtil.getMemberId(); + Long memberId = authenticationUtil.getUserId(); String nickName = authenticationUtil.getUsername(); log.info("[CustomAuthenticationSuccessHandler] providerId={}, role={}, memberId={}", providerId, role, memberId); @@ -44,7 +43,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String refreshToken = jwtUtil.createRefreshToken(memberId, providerId, nickName); // refresh token 저장 - jwtService.storeRefreshToken(refreshToken); + jwtService.storeRefreshToken(refreshToken, memberId); log.info("[CustomAuthenticationSuccessHandler], refreshToken={}", refreshToken); LoginResponse data = LoginResponse.builder() diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java index 9d7645d..2473344 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java @@ -45,6 +45,8 @@ public void logout(HttpServletRequest request, RefreshTokenRequest tokenRequest) String accessToken = jwtUtil.extractAccessToken(request) .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_INVALID_ACCESS_TOKEN)); + log.info("LogOut Access Token: {}", accessToken); + String refreshToken = tokenRequest.getRefreshToken(); jwtUtil.validateToken(refreshToken); if (!"refresh".equals(jwtUtil.getTokenType(refreshToken))) { @@ -56,13 +58,13 @@ public void logout(HttpServletRequest request, RefreshTokenRequest tokenRequest) invalidAccessToken(accessToken); } - public ReissueResponse reissueTokens(RefreshTokenRequest tokenRequest) { + public ReissueResponse reissueTokens(RefreshTokenRequest tokenRequest, Long userId) { String refreshToken = tokenRequest.getRefreshToken(); jwtUtil.validateToken(refreshToken); if (!"refresh".equals(jwtUtil.getTokenType(refreshToken))) { throw new CustomJwtException(ErrorCode.INVALID_REFRESH_TYPE); } - return reissueAndSendTokens(refreshToken); + return reissueAndSendTokens(refreshToken, userId); } public void checkLogout(String accessToken) { @@ -72,30 +74,30 @@ public void checkLogout(String accessToken) { } } - public void storeRefreshToken(String refreshToken) { - redisService.setValues(REFRESH_TOKEN_KEY_PREFIX, refreshToken, Duration.ofMillis(REFRESH_TOKEN_EXPIRED_IN)); + public void storeRefreshToken(String refreshToken, Long userId) { + redisService.setValues(REFRESH_TOKEN_KEY_PREFIX+refreshToken, String.valueOf(userId), Duration.ofMillis(REFRESH_TOKEN_EXPIRED_IN)); } private void deleteRefreshToken(String refreshToken){ if(refreshToken == null){ throw new CustomJwtException(ErrorCode.INVALID_REFRESH_TYPE); } - redisService.delete(refreshToken); + redisService.delete(REFRESH_TOKEN_KEY_PREFIX+refreshToken); } - private void invalidAccessToken(String accessToken) { + public void invalidAccessToken(String accessToken) { redisService.setValues(accessToken, LOGOUT_VALUE, Duration.ofMillis(ACCESS_TOKEN_EXPIRED_IN)); } - private ReissueResponse reissueAndSendTokens(String refreshToken) { + private ReissueResponse reissueAndSendTokens(String refreshToken, Long userId) { // 새로운 Refresh Token 발급 String reissuedAccessToken = jwtUtil.createAccessToken(jwtUtil.getUserId(refreshToken), jwtUtil.getProviderId(refreshToken), jwtUtil.getRole(refreshToken), jwtUtil.getName(refreshToken)); String reissuedRefreshToken = jwtUtil.createRefreshToken(jwtUtil.getUserId(refreshToken), jwtUtil.getProviderId(refreshToken), jwtUtil.getName(refreshToken)); // 새로운 Refresh Token을 DB나 Redis에 저장 - storeRefreshToken(reissuedRefreshToken); + storeRefreshToken(reissuedRefreshToken, userId); // 기존 Refresh Token 폐기 (DB나 Redis에서 삭제) deleteRefreshToken(refreshToken); diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/util/AuthenticationUtil.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/util/AuthenticationUtil.java index 6a133a2..5aeb900 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/util/AuthenticationUtil.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/util/AuthenticationUtil.java @@ -26,7 +26,7 @@ public String getRole() { return grantedAuthority.getAuthority(); } - public Long getMemberId() { + public Long getUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); UserPrincipal principal = (UserPrincipal) authentication.getPrincipal(); return principal.getUserId(); diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java index 89daa37..e0ed192 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java @@ -7,6 +7,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.user.service.UserService; import com.WhoIsRoom.WhoIs_Server.global.common.resolver.CurrentUserId; import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseResponse; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -44,4 +45,11 @@ public BaseResponse updatePassword(@CurrentUserId Long userId, userService.updateMyPassword(userId, request); return BaseResponse.ok(null); } + + @PostMapping("/delete/account") + public BaseResponse deleteAccount(HttpServletRequest request, + @CurrentUserId Long userId) { + userService.deleteAccount(request, userId); + return BaseResponse.ok(null); + } } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java index 52a85a4..a0323b8 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java @@ -2,7 +2,10 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.PasswordRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; +import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; +import com.WhoIsRoom.WhoIs_Server.domain.auth.util.JwtUtil; import com.WhoIsRoom.WhoIs_Server.domain.club.model.Club; import com.WhoIsRoom.WhoIs_Server.domain.club.repository.ClubRepository; import com.WhoIsRoom.WhoIs_Server.domain.member.model.Member; @@ -15,12 +18,15 @@ import com.WhoIsRoom.WhoIs_Server.domain.user.repository.UserRepository; import com.WhoIsRoom.WhoIs_Server.global.common.exception.BusinessException; import com.WhoIsRoom.WhoIs_Server.global.common.response.ErrorCode; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.net.http.HttpRequest; import java.util.*; import java.util.stream.Collectors; @@ -33,6 +39,8 @@ public class UserService { private final MailService mailService; private final MemberRepository memberRepository; private final ClubRepository clubRepository; + private final JwtUtil jwtUtil; + private final JwtService jwtService; @Transactional public void signUp(SignupRequest request) { @@ -169,4 +177,19 @@ private void validateClubExistence(Set clubIds) { throw new BusinessException(ErrorCode.CLUB_NOT_FOUND); } } + + public void deleteAccount (HttpServletRequest request, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + userRepository.delete(user); + + String accessToken = jwtUtil.extractAccessToken(request) + .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_INVALID_ACCESS_TOKEN)); + if (accessToken != null) { + jwtService.invalidAccessToken(accessToken); // Redis에 blacklist 저장 + } + + SecurityContextHolder.clearContext(); + } } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisInitializer.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisInitializer.java new file mode 100644 index 0000000..dea6697 --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisInitializer.java @@ -0,0 +1,26 @@ +package com.WhoIsRoom.WhoIs_Server.global.common.redis; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@Profile("local") +@RequiredArgsConstructor +public class RedisInitializer { + + private final RedisTemplate redisTemplate; + + @PostConstruct + public void clearRedis() { + redisTemplate.getConnectionFactory() + .getConnection() + .flushDb(); // 선택한 DB(예: database: 0)만 초기화 + log.info("✅ Redis DB 초기화 완료"); + } +} +