diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 50805e1c..e92513ab 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -156,7 +157,7 @@ public String updateMenuSortOrder(List requests, MemberDe } Map idToSort = requests.stream() - .collect(java.util.stream.Collectors.toMap(MenuSortUpdateRequest::getMenuId, + .collect(Collectors.toMap(MenuSortUpdateRequest::getMenuId, MenuSortUpdateRequest::getSortOrder)); menus.forEach(m -> m.updateSortOrder(idToSort.get(m.getId()))); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java index db6f7a69..20e844e9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/config/security/SecurityConfig.java @@ -14,7 +14,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; -import com.nowait.applicationuser.oauth.oauth2.CustomOAuth2UserService; +import com.nowait.applicationuser.oauth.service.CustomOAuth2UserService; import com.nowait.applicationuser.oauth.oauth2.OAuth2LoginSuccessHandler; import com.nowait.applicationuser.security.jwt.JwtAuthorizationFilter; import com.nowait.applicationuser.security.jwt.JwtUtil; @@ -58,6 +58,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/oauth2/authorization/kakao", // 카카오 로그인 요청 "/login/oauth2/code/**", // 카카오 인증 콜백 "/api/refresh-token", // refresh token (토큰 갱신) + "/v2/app/oauth/kakao/login", // 카카오 앱 로그인 "/v1/menus/**", // 모든 메뉴 조회 "/v1/store-payments/**", // 결제 관련 API "/orders/**", diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java new file mode 100644 index 00000000..0b744df0 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/controller/KakaoAppAuthController.java @@ -0,0 +1,35 @@ +package com.nowait.applicationuser.oauth.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginRequest; +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginResponse; +import com.nowait.applicationuser.oauth.service.KakaoAppLoginService; +import com.nowait.common.api.ApiUtils; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/v2/app/oauth/kakao") +@RequiredArgsConstructor +public class KakaoAppAuthController { + + private final KakaoAppLoginService kakaoAppLoginService; + + @PostMapping("/login") + public ResponseEntity kakaoAppLogin(@RequestBody KakaoAppLoginRequest request) { + + KakaoAppLoginResponse response = kakaoAppLoginService.login(request); + + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success(response) + ); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginRequest.java new file mode 100644 index 00000000..241b8604 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginRequest.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.oauth.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class KakaoAppLoginRequest { + private String kakaoAccessToken; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginResponse.java new file mode 100644 index 00000000..4c6f36b5 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/KakaoAppLoginResponse.java @@ -0,0 +1,22 @@ +package com.nowait.applicationuser.oauth.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class KakaoAppLoginResponse{ + private String accessToken; + private String refreshToken; + private Long userId; + private String email; + private String nickName; + private String profileImage; + private boolean phoneEntered; + private boolean marketingAgree; + private boolean isNewUser; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java new file mode 100644 index 00000000..01532eb5 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/dto/OAuthUserResult.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.oauth.dto; + +import com.nowait.domaincorerdb.user.entity.User; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class OAuthUserResult { + private final User user; + private final boolean newUser; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java index 13dc0ecd..b741f9be 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java @@ -1,8 +1,6 @@ package com.nowait.applicationuser.oauth.oauth2; import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Optional; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; @@ -10,9 +8,8 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import com.nowait.applicationuser.security.jwt.JwtUtil; -import com.nowait.domaincorerdb.token.entity.Token; -import com.nowait.domaincorerdb.token.repository.TokenRepository; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; @@ -30,8 +27,7 @@ @RequiredArgsConstructor @Slf4j public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final JwtUtil jwtUtil; - private final TokenRepository tokenRepository; + private final AuthTokenService authTokenService; @Override @Transactional @@ -40,26 +36,11 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal(); User user = customUserDetails.getUser(); - Long userId = customUserDetails.getUserId(); - String role = authentication.getAuthorities().iterator().next().getAuthority(); - // JWT 발급 - String accessToken = jwtUtil.createAccessToken("accessToken", userId, role, - Boolean.TRUE.equals(user.getPhoneEntered()), Boolean.TRUE.equals(user.getIsMarketingAgree()),60 * 60 * 1000L); // 1시간 - String refreshToken = jwtUtil.createRefreshToken("refreshToken", userId, 30L * 24 * 60 * 60 * 1000L); // 30일 - - // 1. refreshToken을 DB에 저장 or update - Optional tokenOptional = tokenRepository.findByUserId(user.getId()); - if (tokenOptional.isPresent()) { - Token token = tokenOptional.get(); - token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); - } else { - Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); - tokenRepository.save(token); - } + AuthenticationResponse authenticationResponse = authTokenService.issueTokens(user); // 2. refreshToken을 HttpOnly 쿠키로 설정 (ResponseCookie로) - ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken) + ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", authenticationResponse.getRefreshToken()) .httpOnly(true) .secure(false) // 운영환경에서는 true .path("/") @@ -71,7 +52,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("Set-Cookie", refreshTokenCookie.toString()); // 3. 프론트엔드로 리다이렉트 (accessToken만 쿼리로 전달) - String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + accessToken; + String targetUrl = "https://app.nowait.co.kr/login/success?accessToken=" + authenticationResponse.getAccessToken(); response.sendRedirect(targetUrl); } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java similarity index 57% rename from nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java rename to nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java index 048dc43b..09b29e9a 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/CustomOAuth2UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/CustomOAuth2UserService.java @@ -1,7 +1,4 @@ -package com.nowait.applicationuser.oauth.oauth2; - -import java.time.LocalDateTime; -import java.util.Optional; +package com.nowait.applicationuser.oauth.service; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -11,10 +8,8 @@ import com.nowait.applicationuser.oauth.dto.KaKaoResponse; import com.nowait.applicationuser.oauth.dto.OAuth2Response; -import com.nowait.common.enums.Role; -import com.nowait.common.enums.SocialType; +import com.nowait.applicationuser.oauth.dto.OAuthUserResult; import com.nowait.domaincorerdb.user.entity.User; -import com.nowait.domaincorerdb.user.repository.UserRepository; import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; import lombok.RequiredArgsConstructor; @@ -25,12 +20,13 @@ @RequiredArgsConstructor @Slf4j public class CustomOAuth2UserService extends DefaultOAuth2UserService { - private final UserRepository userRepository; + private final OAuthUserService oAuthUserService; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); + // TODO : 해당 로그 필요한지 추후 확인 필요 log.info("CustomOAuth2UserService :: {}", oAuth2User); log.info("oAuthUser.getAttributes :: {}", oAuth2User.getAttributes()); @@ -44,34 +40,10 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } // DB에 유저가 있는지 판단 - Optional foundUser = userRepository.findByEmail(oAuth2Response.getEmail()); - - // DB에 유저 없으면 - 회원가입 - if (foundUser.isEmpty()) { - - User user = User.builder() - .email(oAuth2Response.getEmail()) - .phoneNumber("") - .nickname(oAuth2Response.getNickName()) - .profileImage(oAuth2Response.getProfileImage()) - .socialType(SocialType.KAKAO) - .role(Role.USER) // 일반 유저 설정 - .storeId(0L) - .phoneEntered(false) - .isMarketingAgree(false) - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - userRepository.save(user); + OAuthUserResult result = oAuthUserService.loadOrCreateUser(oAuth2Response); + User user = result.getUser(); + boolean newUser = result.isNewUser(); - return new CustomOAuth2User(user); - } else { - // DB에 유저 존재하면 - 로그인 진행 (이때 로그인 처리는 안하고, OAuth2LoginSuccessHandler에서 담당함) - User user = foundUser.get(); - - return new CustomOAuth2User(user); - } + return new CustomOAuth2User(user, newUser); } - } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java new file mode 100644 index 00000000..722dd223 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/KakaoAppLoginService.java @@ -0,0 +1,73 @@ +package com.nowait.applicationuser.oauth.service; + +import java.time.Instant; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginRequest; +import com.nowait.applicationuser.oauth.dto.KakaoAppLoginResponse; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.applicationuser.token.service.AuthTokenService; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class KakaoAppLoginService { + + private final CustomOAuth2UserService customOAuth2UserService; + private final ClientRegistrationRepository clientRegistrationRepository; + private final AuthTokenService authTokenService; + + public KakaoAppLoginResponse login(KakaoAppLoginRequest request) { + String kakaoAccessTokenValue = request.getKakaoAccessToken(); + + if (kakaoAccessTokenValue == null || kakaoAccessTokenValue.isEmpty()) { + throw new OAuth2AuthenticationException("Kakao Access Token is missing."); + } + + ClientRegistration kakaoRegistration = clientRegistrationRepository.findByRegistrationId("kakao"); + + if (kakaoRegistration == null) { + throw new OAuth2AuthenticationException("Kakao Client Registration not found."); + } + + OAuth2AccessToken kakaoAccessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + kakaoAccessTokenValue, + Instant.now(), + null + ); + + OAuth2UserRequest userRequest = new OAuth2UserRequest( + kakaoRegistration, + kakaoAccessToken + ); + + OAuth2User oAuth2User = customOAuth2UserService.loadUser(userRequest); + CustomOAuth2User customUser = (CustomOAuth2User) oAuth2User; + User user = customUser.getUser(); + + AuthenticationResponse authenticationResponse = authTokenService.issueTokens(user); + + return KakaoAppLoginResponse.builder() + .accessToken(authenticationResponse.getAccessToken()) + .refreshToken(authenticationResponse.getRefreshToken()) + .userId(user.getId()) + .email(user.getEmail()) + .nickName(user.getNickname()) + .profileImage(user.getProfileImage()) + .phoneEntered(user.getPhoneEntered()) + .marketingAgree(user.getIsMarketingAgree()) + .isNewUser(customUser.isNewUser()) + .build(); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java new file mode 100644 index 00000000..96f5063f --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/service/OAuthUserService.java @@ -0,0 +1,49 @@ +package com.nowait.applicationuser.oauth.service; + +import java.time.LocalDateTime; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.oauth.dto.OAuth2Response; +import com.nowait.applicationuser.oauth.dto.OAuthUserResult; +import com.nowait.common.enums.Role; +import com.nowait.common.enums.SocialType; +import com.nowait.domaincorerdb.user.entity.User; +import com.nowait.domaincorerdb.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OAuthUserService { + private final UserRepository userRepository; + + @Transactional + public OAuthUserResult loadOrCreateUser(OAuth2Response oAuth2Response) { + return userRepository.findByEmail(oAuth2Response.getEmail()) + .map(user -> new OAuthUserResult(user, false)) // 기존 유저 + .orElseGet(() -> { + User created = createUser(oAuth2Response); + return new OAuthUserResult(created, true); // 신규 유저 + }); + } + + private User createUser(OAuth2Response oAuth2Response) { + User user = User.builder() + .email(oAuth2Response.getEmail()) + .phoneNumber("") + .nickname(oAuth2Response.getNickName()) + .profileImage(oAuth2Response.getProfileImage()) + .socialType(SocialType.KAKAO) + .role(Role.USER) // 일반 유저 설정 + .storeId(0L) + .phoneEntered(false) + .isMarketingAgree(false) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + return userRepository.save(user); + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java index 53368747..25a79ba9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java @@ -10,7 +10,7 @@ import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.service.TokenService; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; @@ -26,7 +26,7 @@ @Slf4j public class TokenController { private final JwtUtil jwtUtil; - private final TokenService tokenService; + private final AuthTokenService tokenService; private final UserRepository userRepository; @Value("${jwt.access-token-expiration-ms}") private long accessTokenExpiration; diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java deleted file mode 100644 index f42e7979..00000000 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/NewAccessTokenResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.nowait.applicationuser.token.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -@RequiredArgsConstructor -@Getter -@ToString(exclude = {"accessToken"}) // 로깅 시 토큰 노출 방지 -public class NewAccessTokenResponse { - @JsonProperty("access_token") - private final String accessToken; -} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java index c0bf4e68..ac4b9c5c 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/dto/RefreshTokenRequest.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +// TODO : 사용하는 DTO인지 확인 필요 @Getter @NoArgsConstructor public class RefreshTokenRequest { diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java new file mode 100644 index 00000000..dc5f3f61 --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/AuthTokenService.java @@ -0,0 +1,115 @@ +package com.nowait.applicationuser.token.service; + +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.nowait.applicationuser.security.exception.RefreshTokenNotFoundException; +import com.nowait.applicationuser.security.exception.TokenBadRequestException; +import com.nowait.applicationuser.security.jwt.JwtUtil; +import com.nowait.applicationuser.token.dto.AuthenticationResponse; +import com.nowait.domaincorerdb.token.entity.Token; +import com.nowait.domaincorerdb.token.repository.TokenRepository; +import com.nowait.domaincorerdb.user.entity.User; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AuthTokenService { + + private final JwtUtil jwtUtil; + private final TokenRepository tokenRepository; + + @Transactional + public boolean validateToken(String token, Long userId){ + // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. + Optional savedToken = tokenRepository.findByUserId(userId); + + // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 + if (savedToken.isEmpty()){ + log.debug("DB에 현재 userId에 대응되는 리프레시 토큰이 없습니다"); + return false; + } + + // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 + if (!savedToken.get().getRefreshToken().equals(token)){ + log.debug("DB에 저장된 리프레시 토큰와 현재 전달받은 리프레시 토큰 일치하지 않습니다"); + return false; + } + + // 리프레시 토큰의 만료여부 확인 + if(jwtUtil.isExpired(token)){ + log.debug("리프레시 토큰이 만료된 토큰입니다"); + return false; // 만료된 토큰은 유효하지 않음 + } + + log.info("리프레시 토큰이 유효한 토큰입니다"); + return true; // 모든 조건 만족 시, 유효한 토큰 + } + + @Transactional + public AuthenticationResponse issueTokens(User user) { + Long userId = user.getId(); + String role = user.getRole().name(); + + String accessToken = jwtUtil.createAccessToken( + "accessToken", + userId, + role, + Boolean.TRUE.equals(user.getPhoneEntered()), + Boolean.TRUE.equals(user.getIsMarketingAgree()), + 60 * 60 * 2000L + ); // 2시간 + + String refreshToken = jwtUtil.createRefreshToken( + "refreshToken", + userId, + 30L * 24 * 60 * 60 * 1000L + ); // 30일 + + newUpdateRefreshToken(user, refreshToken); + return new AuthenticationResponse(accessToken, refreshToken); + } + + @Transactional + public void newUpdateRefreshToken(User user, String refreshToken) { + Optional tokenOptional = tokenRepository.findByUserId(user.getId()); + if (tokenOptional.isPresent()) { + Token token = tokenOptional.get(); + token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30)); + } else { + Token token = Token.toEntity(user, refreshToken, LocalDateTime.now().plusDays(30)); + tokenRepository.save(token); + } + } + + @Transactional + public void updateRefreshToken(Long userId, String oldRefreshToken, String newRefreshToken){ + Token token = tokenRepository.findByUserId(userId) + .orElseThrow(RefreshTokenNotFoundException::new); // 404 + + if (!token.getRefreshToken().equals(oldRefreshToken)){ + throw new TokenBadRequestException(); // 400 + } + + // 기존 토큰 삭제 및 새 토큰 저장 + token.updateRefreshToken(newRefreshToken, LocalDateTime.now().plusDays(30)); + } + + @Getter + public static class TokenResult { + private final String accessToken; + private final String refreshToken; + + public TokenResult(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + } +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java deleted file mode 100644 index eee87b82..00000000 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/service/TokenService.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.nowait.applicationuser.token.service; - -import java.time.LocalDateTime; -import java.util.Optional; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.nowait.applicationuser.security.exception.RefreshTokenNotFoundException; -import com.nowait.applicationuser.security.exception.TokenBadRequestException; -import com.nowait.applicationuser.security.jwt.JwtUtil; -import com.nowait.domaincorerdb.token.entity.Token; -import com.nowait.domaincorerdb.token.repository.TokenRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Service -@RequiredArgsConstructor -@Slf4j -public class TokenService { - private final TokenRepository tokenRepository; - private final JwtUtil jwtUtil; - - @Transactional - public boolean validateToken(String token, Long userId){ - // DB에서 해당 userId와 일치하는 리프레시토큰을 찾는다. - Optional savedToken = tokenRepository.findByUserId(userId); - - // DB에서 userId에 대응되는 리프레시토큰 없으면, 유효하지 않음 - if (savedToken.isEmpty()){ - log.debug("DB에 현재 userId에 대응되는 리프레시 토큰이 없습니다"); - return false; - } - - // 리프레시 토큰이 DB에 저장된 토큰과 일치하는지 확인 - if (!savedToken.get().getRefreshToken().equals(token)){ - log.debug("DB에 저장된 리프레시 토큰와 현재 전달받은 리프레시 토큰 일치하지 않습니다"); - return false; - } - - // 리프레시 토큰의 만료여부 확인 - if(jwtUtil.isExpired(token)){ - log.debug("리프레시 토큰이 만료된 토큰입니다"); - return false; // 만료된 토큰은 유효하지 않음 - } - - log.info("리프레시 토큰이 유효한 토큰입니다"); - return true; // 모든 조건 만족 시, 유효한 토큰 - } - - @Transactional - public void updateRefreshToken(Long userId, String oldRefreshToken, String newRefreshToken){ - Token token = tokenRepository.findByUserId(userId) - .orElseThrow(RefreshTokenNotFoundException::new); // 404 - - if (!token.getRefreshToken().equals(oldRefreshToken)){ - throw new TokenBadRequestException(); // 400 - } - - // 기존 토큰 삭제 및 새 토큰 저장 - token.updateRefreshToken(newRefreshToken, LocalDateTime.now().plusDays(30)); - } -} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java index b0179d21..91ab09ca 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/controller/UserController.java @@ -2,14 +2,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.dto.NewAccessTokenResponse; import com.nowait.applicationuser.user.dto.UserUpdateRequest; import com.nowait.applicationuser.user.service.UserService; import com.nowait.common.api.ApiUtils; @@ -28,14 +26,14 @@ public class UserController { public ResponseEntity putOptional( @Valid @RequestBody UserUpdateRequest req) { - NewAccessTokenResponse newAccessTokenResponse = userService.putOptional(req.phoneNumber(), + AuthenticationResponse authenticationResponse = userService.putOptional(req.phoneNumber(), Boolean.TRUE.equals(req.consent()), req.accessToken()); return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( - newAccessTokenResponse + authenticationResponse ) ); } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java index 38496b9f..5820b224 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/user/service/UserService.java @@ -7,8 +7,7 @@ import com.nowait.applicationuser.security.jwt.JwtUtil; import com.nowait.applicationuser.token.dto.AuthenticationResponse; -import com.nowait.applicationuser.token.dto.NewAccessTokenResponse; -import com.nowait.applicationuser.token.service.TokenService; +import com.nowait.applicationuser.token.service.AuthTokenService; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; import com.nowait.domaincorerdb.user.repository.UserRepository; @@ -20,15 +19,13 @@ public class UserService { private final UserRepository userRepository; - private final TokenService tokenService; + private final AuthTokenService authTokenService; private final JwtUtil jwtUtil; @Transactional - public NewAccessTokenResponse putOptional(String phoneNumber, boolean consent, String accessToken) { + public AuthenticationResponse putOptional(String phoneNumber, boolean consent, String accessToken) { - Long userId = jwtUtil.getUserId(accessToken);; - String role = jwtUtil.getRole(accessToken); - AuthenticationResponse authenticationResponse; + Long userId = jwtUtil.getUserId(accessToken); User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); @@ -39,17 +36,6 @@ public NewAccessTokenResponse putOptional(String phoneNumber, boolean consent, S user.setPhoneNumberAndMarkEntered(phoneNumber, LocalDateTime.now()); user.setIsMarketingAgree(consent, LocalDateTime.now()); - String newAccessToken = jwtUtil.createAccessToken( - "accessToken", - userId, - role, - Boolean.TRUE.equals(user.getPhoneEntered()), - Boolean.TRUE.equals(user.getIsMarketingAgree()), - 60 * 60 * 1000L - ); - - NewAccessTokenResponse newAccessTokenResponse = new NewAccessTokenResponse(newAccessToken); - - return newAccessTokenResponse; + return authTokenService.issueTokens(user); } } diff --git a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java index f163bca7..544c91c7 100644 --- a/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java +++ b/nowait-domain/domain-user-rdb/src/main/java/com/nowait/domainuserrdb/oauth/dto/CustomOAuth2User.java @@ -9,16 +9,14 @@ import com.nowait.domaincorerdb.user.entity.User; +import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor +@Getter public class CustomOAuth2User implements OAuth2User { - private User user; - - // User 객체를 받는 생성자 - public CustomOAuth2User(User user) { - this.user = user; - } + private final User user; + private final boolean newUser; @Override public Map getAttributes() { @@ -40,6 +38,12 @@ public String getAuthority() { return authorities; } + // JWT 인증 시 (항상 기존 유저 취급) + public CustomOAuth2User(User user) { + this.user = user; + this.newUser = false; + } + @Override public String getName() { return user.getEmail();