Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4d34ffd
[SPOT-301][FEATURE] Feign Client 관련 Config 생성
msk226 Jul 31, 2025
450d987
[SPOT-301][FEATURE] 전략 패턴을 사용하기 위한 인터페이스 정의
msk226 Aug 1, 2025
f7a9e3a
[SPOT-301][FEATURE] 카카오 외부 API 호출 관련 Feign 로직 구현
msk226 Aug 1, 2025
1bae48e
[SPOT-301][FEATURE] 구글 외부 API 호출 관련 Feign 로직 구현
msk226 Aug 1, 2025
7d6b853
[SPOT-301][FEATURE] Auth 및 JWT 상수 관련 클래스 정의
msk226 Aug 1, 2025
24b023e
[SPOT-301][FEATURE] Feign 관련 Config 클래스 애노테이션 부착
msk226 Aug 1, 2025
7e73c68
[SPOT-301][FEATURE] 구글 관련 전략 클래스 생성
msk226 Aug 1, 2025
ef85266
[SPOT-301][FEATURE] 카카오 관련 전략 클래스 생성
msk226 Aug 1, 2025
2acf990
[SPOT-301][FEATURE] 전략 팩토리 클래스 생성
msk226 Aug 1, 2025
b476cc6
[SPOT-301][FEATURE] 회원 생성 정적 팩토리 메서드 정의
msk226 Aug 1, 2025
3b139bb
[SPOT-301][FEATURE] 각 전략 별 회원 생성 정적 팩토리 메서드를 활용한 회원 생성 메서드 구현
msk226 Aug 1, 2025
372026d
[SPOT-301][FEATURE] 회원 로그인 및 회원 가입 처리 로직 별도 클래스로 분리
msk226 Aug 1, 2025
9c0c7b3
[SPOT-301][FEATURE] 전략 패턴 기반 OAuth 로그인 관련 서비스 클래스 정의
msk226 Aug 1, 2025
c437495
[SPOT-301][FEATURE] 소셜 로그인 관련 외부 환경 변수 Prefix 통일
msk226 Aug 1, 2025
71d58fa
[SPOT-301][FEATURE] 회원 가입 관련 로직 서비스 메서드 트랜잭션 애노테이션 부착
msk226 Aug 1, 2025
0a1d20f
[SPOT-301][FEATURE] Feign Client 스캔 Base Package 프로젝트 상위 디렉토리로 지정
msk226 Aug 1, 2025
3a11293
[SPOT-301][FEATURE] 불필요한 레거시 소셜 로그인 관련 코드 삭제
msk226 Aug 1, 2025
aaae67b
[SPOT-301][FEATURE] 소셜 로그인 관련 DTO 패키지 정리
msk226 Aug 1, 2025
e84c6eb
[SPOT-301][FEATURE] 네이버 소셜 로그인 전략 패턴 적용 위해 기존 코드 삭제
msk226 Aug 1, 2025
f051a6b
[SPOT-301][FEATURE] 네이버 소셜 로그인을 위한 외부 API 호출 Feign Client 정의
msk226 Aug 1, 2025
bba69cc
[SPOT-301][FEATURE] 네이버 소셜 로그인을 위한 DTO 정의
msk226 Aug 1, 2025
ec3217e
[SPOT-301][FEATURE] 네이버 관련 전략 클래스 생성
msk226 Aug 1, 2025
e586445
[SPOT-301][FEATURE] 불필요한 카카오 관련 레거시 외부 API 호출 클래스 삭제
msk226 Aug 1, 2025
bd0c03d
[SPOT-301][FEATURE] Token 관련 클래스 명에서 JWT와 같은 기술 구체적인 네이밍 제거
msk226 Aug 1, 2025
8060f9f
[SPOT-301][FEATURE] 불필요한 OAuth 관련 의존성 및 코드 삭제
msk226 Aug 1, 2025
e97dd5e
[SPOT-301][REFACTOR] OAuth 전략 패키지 구조 정리 및 오타 수정
msk226 Aug 1, 2025
ff4b0f0
[SPOT-301][FEATURE] 소셜 로그인 관련 컨트롤러 정의
msk226 Aug 1, 2025
a39857e
[SPOT-301][FEATURE] 외부 API 호출 관련 헤더 상수화
msk226 Aug 1, 2025
e322df3
[SPOT-301][FEATURE] 소셜 로그인 관련 회원 생성 팩토리 메서드 명 내 OAuth 키워드 추가
msk226 Aug 1, 2025
2456096
[SPOT-301][FEATURE] Feign 예외를 통일하여 처리하기 위한 래퍼 클래스 정의
msk226 Aug 1, 2025
86aaaf5
[SPOT-301][FEATURE] constants 관련 클래스 abstract로 변경
msk226 Aug 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

plugins {
id 'java'
id 'jacoco'
Expand Down Expand Up @@ -27,24 +26,21 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// h2
implementation 'com.h2database:h2'
// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'
// apache common csv
implementation 'org.apache.commons:commons-csv:1.8'

// Oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// h2
implementation 'com.h2database:h2'
// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'
// apache common csv
implementation 'org.apache.commons:commons-csv:1.8'

// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
Expand Down Expand Up @@ -81,6 +77,9 @@ dependencies {
// P6spy
implementation 'p6spy:p6spy:3.9.1'
implementation 'com.github.gavlyukovskiy:datasource-decorator-spring-boot-autoconfigure:1.9.0'

// Feign
implementation "org.springframework.cloud:spring-cloud-starter-openfeign:4.1.3"
}

sentry {
Expand Down Expand Up @@ -119,7 +118,6 @@ jacocoTestCoverageVerification {
}



tasks.named('test') {
useJUnitPlatform()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package com.example.spot.auth.application.legacy;

import com.example.spot.member.presentation.dto.MemberRequestDTO.SignUpDetailDTO;
import com.example.spot.auth.presentation.dto.rsa.Rsa;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO;
import com.example.spot.member.presentation.dto.MemberRequestDTO;
import com.example.spot.member.presentation.dto.MemberRequestDTO.SignUpDetailDTO;
import com.example.spot.member.presentation.dto.MemberResponseDTO;
import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO;
import com.example.spot.auth.presentation.dto.naver.NaverCallback;
import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

Expand All @@ -19,12 +15,6 @@ public interface AuthService {

MemberResponseDTO.InactiveMemberDTO withdraw();

void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response);

SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception;

SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception;

MemberResponseDTO.MemberSignInDTO signIn(Long httpSession, MemberRequestDTO.SignInDTO signInDTO) throws Exception;

Rsa.RSAPublicKey getRSAPublicKey() throws Exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
import com.example.spot.auth.domain.VerificationCode;
import com.example.spot.auth.domain.rsa.RSAKeyRepository;
import com.example.spot.auth.domain.verification.VerificationCodeRepository;
import com.example.spot.auth.presentation.dto.naver.NaverCallback;
import com.example.spot.auth.presentation.dto.naver.NaverMember;
import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken;
import com.example.spot.auth.presentation.dto.rsa.Rsa;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO;
Expand All @@ -18,7 +15,6 @@
import com.example.spot.common.api.exception.handler.MemberHandler;
import com.example.spot.common.application.message.MailService;
import com.example.spot.common.security.utils.JwtTokenProvider;
import com.example.spot.common.security.utils.MemberUtils;
import com.example.spot.common.security.utils.RSAUtils;
import com.example.spot.common.security.utils.SecurityUtils;
import com.example.spot.member.domain.Member;
Expand All @@ -35,7 +31,6 @@
import com.example.spot.member.presentation.dto.MemberResponseDTO;
import com.example.spot.member.presentation.dto.MemberResponseDTO.CheckMemberDTO;
import com.example.spot.member.presentation.dto.MemberResponseDTO.NicknameDuplicateDTO;
import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO;
import com.example.spot.study.domain.repository.StudyMemberRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
Expand Down Expand Up @@ -76,7 +71,6 @@ public class AuthServiceImpl implements AuthService {
private final StudyJoinReasonRepository studyJoinReasonRepository;

private final MailService mailService;
private final NaverOAuthService naverOAuthService;

private final RSAUtils rsaUtils;
private final RSAKeyRepository rsaKeyRepository;
Expand Down Expand Up @@ -127,171 +121,6 @@ public MemberResponseDTO.InactiveMemberDTO withdraw() {
return MemberResponseDTO.InactiveMemberDTO.toDTO(member);
}

/* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */

/**
* 네이버 로그인 인증 요청 URL로 실제 요청을 전송하고 로그인 페이지로 리디렉션하는 메서드입니다.
*
* @param request : HTTPServletRequest
* @param response : HttpServletResponse
*/
@Override
public void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response) {
String url = naverOAuthService.getNaverAuthorizeUrl();
try {
response.sendRedirect(url);
} catch (Exception e) {
throw new MemberHandler(ErrorStatus._NAVER_SIGN_IN_INTEGRATION_FAILED);
}
}

/**
* SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. 로그인 Callback 성공시 반환되는 naverCallback을 바탕으로 액세스 토큰을 발급받고 프로필에 접근합니다. 현재
* SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 기반으로 회원 정보를 생성하여 DB에 저장합니다. 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다.
*
* @param request : HttpServletRequest
* @param response : HttpServletResponse
* @param naverCallback : Callback 함수 성공시 반환되는 요소(code, state, error, error_description)
* @return SocialLoginSignInDTO(isSpotMember, signInDTO - 토큰정보)
*/
@Override
public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response,
NaverCallback naverCallback) throws Exception {
NaverMember.ResponseDTO responseDTO = naverOAuthService.getNaverMember(request, response, naverCallback);
return getSocialLoginSignInDTO(responseDTO);
}

/**
* SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. 클라이언트로부터 전달받은 액세스 토큰을 통해 프로필에 접근합니다. 현재 SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를
* 기반으로 회원 정보를 생성하여 DB에 저장합니다. 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다.
*
* @param request : HttpServletRequest
* @param response : HttpServletResponse
* @return SocialLoginSignInDTO(isSpotMember, signInDTO - 토큰정보)
*/
@Override
public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response,
NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception {
NaverMember.ResponseDTO responseDTO = naverOAuthService.getNaverMember(request, response, naverTokenDTO);
return getSocialLoginSignInDTO(responseDTO);
}

/**
* 네이버 회원 프로필을 통해 SocialLoginSignInDTO를 생성하는 함수입니다.
*
* @param responseDTO : 네이버 회원 프로필 DTO
* @return SocialLoginSignInDTO (SPOT 회원 정보 및 토큰 정보)
*/
private SocialLoginSignInDTO getSocialLoginSignInDTO(NaverMember.ResponseDTO responseDTO) {
String email = responseDTO.getResponse().getEmail();

// 다른 로그인 방식을 사용한 계정이 있는지 확인
if (memberRepository.existsByEmailAndLoginTypeNot(email, LoginType.NAVER)) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND));

// 탈퇴한(inactive) 회원이면 기존 정보 삭제
if (member.getInactive() != null) {
refreshTokenRepository.deleteByMemberId(member.getId());
memberRepository.deleteById(member.getId());
entityManager.flush();
} else {
throw new MemberHandler(ErrorStatus._MEMBER_EMAIL_ALREADY_EXISTS);
}
}

// 네이버 로그인 계정이 있는지 확인
Boolean isSpotMember = Boolean.TRUE;
if (memberRepository.existsByEmailAndLoginType(email, LoginType.NAVER)) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND));

// 탈퇴한(inactive) 회원이면 기존 정보 삭제 후 회원 정보 저장
if (member.getInactive() != null) {
refreshTokenRepository.deleteByMemberId(member.getId());
memberRepository.deleteById(member.getId());
entityManager.flush();
isSpotMember = Boolean.FALSE;
signUpWithNaver(responseDTO);
}
} else {
isSpotMember = Boolean.FALSE;
signUpWithNaver(responseDTO);
}

Member member = memberRepository.findByEmailAndLoginType(email, LoginType.NAVER)
.orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND));

if (!isMemberExistsByCheckList(member)) {
isSpotMember = Boolean.FALSE;
}

// 로그인을 위한 토큰 발급
TokenDTO token = jwtTokenProvider.createToken(member.getId());
saveRefreshToken(member, token);

MemberResponseDTO.MemberSignInDTO signInDTO = MemberResponseDTO.MemberSignInDTO.builder()
.tokens(token)
.memberId(member.getId())
.loginType(member.getLoginType())
.email(member.getEmail())
.build();

return SocialLoginSignInDTO.toDTO(isSpotMember, signInDTO);
}

public boolean isMemberExistsByCheckList(Member member) {
Long memberId = member.getId();
return preferredThemeRepository.existsByMemberId(memberId) &&
preferredRegionRepository.existsByMemberId(memberId) &&
studyJoinReasonRepository.existsByMemberId(memberId);
}

/**
* 현재 SPOT에 가입되어 있지 않은 회원에 한해 회원 정보를 생성하여 DB에 저장합니다.
*
* @param memberDTO : naverCallback을 바탕으로 생성된 프로필 객체
*/
private void signUpWithNaver(NaverMember.ResponseDTO memberDTO) {
String birthYear = memberDTO.getResponse().getBirthYear();
String birthDay = memberDTO.getResponse().getBirthDay();

LocalDate birth = null;
if (birthYear != null && birthDay != null) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
birth = LocalDate.parse(birthYear + "-" + birthDay, formatter);
}

Gender gender;
if (memberDTO.getResponse().getGender().equals("F")) {
gender = Gender.FEMALE;
} else if (memberDTO.getResponse().getGender().equals("M")) {
gender = Gender.MALE;
} else {
gender = Gender.UNKNOWN;
}

Member member = Member.builder()
.name(memberDTO.getResponse().getName())
.nickname(memberDTO.getResponse().getNickname())
.birth(birth)
.gender(gender)
.email(memberDTO.getResponse().getEmail())
.carrier(Carrier.NONE)
.phone(MemberUtils.generatePhoneNumber())
.loginId(memberDTO.getResponse().getEmail())
.password("")
.profileImage(memberDTO.getResponse().getProfileImage())
.personalInfo(false)
.idInfo(false)
.isAdmin(Boolean.FALSE)
.loginType(LoginType.NAVER)
.status(Status.ON)
.build();

memberRepository.save(member);
}

/* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */

/**
Expand Down Expand Up @@ -638,6 +467,13 @@ public NicknameDuplicateDTO checkNicknameAvailability(String nickname) {
return new NicknameDuplicateDTO(nickname, isDuplicate);
}

public boolean isMemberExistsByCheckList(Member member) {
Long memberId = member.getId();
return preferredThemeRepository.existsByMemberId(memberId) &&
preferredRegionRepository.existsByMemberId(memberId) &&
studyJoinReasonRepository.existsByMemberId(memberId);
}

/**
* 임시 비밀번호를 발급하는 메서드입니다. 알파벳 대소문자, 숫자, 특수기호를 혼합하여 13자리 비밀번호를 생성합니다.
*
Expand Down
Loading