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
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public String redirectURL(LoginType type) {

public SocialLoginSignInDTO loginOrSignUp(LoginType type, String code) {
OAuthStrategy strategy = strategyFactory.getStrategy(type);
return memberProcessor.processOAuthMember(type, strategy.toMember(code));
return memberProcessor.processOAuthMember(strategy.getOAuthProfile(code));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.spot.auth.application.refactor;

import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

public interface TokenProvider {

TokenDTO createToken(Long memberId);

TokenDTO reissueToken(String refreshToken);

boolean isTokenExpired(String token);

boolean validateToken(String token);

Authentication getAuthentication(String token, UserDetails userDetails);

String resolveToken(HttpServletRequest request);

Long getMemberIdByToken(String token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.example.spot.auth.presentation.dto.token.TokenResponseDTO;

public interface TokenService {
public interface TokenReissueService {

// 리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급
TokenResponseDTO.TokenDTO reissueToken(String refreshToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.spot.auth.application.refactor.dto;

import com.example.spot.member.domain.enums.LoginType;

public record OAuthProfile(
LoginType loginType,
String email,
String nickname,
String profileImageUrl
) {

public static OAuthProfile of(LoginType loginType, String email, String nickname, String profileImageUrl) {
return new OAuthProfile(loginType, email, nickname, profileImageUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.spot.auth.application.refactor.dto;

import com.example.spot.member.domain.Member;
import java.util.Optional;

public record SocialAccountResult(
Optional<Member> member
) {
public static SocialAccountResult empty() {
return new SocialAccountResult(Optional.empty());
}

public static SocialAccountResult of(Member member) {
return new SocialAccountResult(Optional.of(member));
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.example.spot.auth.application.refactor.impl;

import com.example.spot.auth.application.refactor.TokenService;
import com.example.spot.auth.application.refactor.TokenProvider;
import com.example.spot.auth.application.refactor.TokenReissueService;
import com.example.spot.auth.domain.RefreshToken;
import com.example.spot.auth.infrastructure.jpa.RefreshTokenRepository;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO;
import com.example.spot.common.api.code.status.ErrorStatus;
import com.example.spot.common.api.exception.GeneralException;
import com.example.spot.common.security.utils.JwtTokenProvider;
import com.example.spot.member.domain.Member;
import com.example.spot.member.infrastructure.jpa.MemberRepository;
import java.util.Objects;
Expand All @@ -17,10 +17,10 @@
@Service
@Transactional
@RequiredArgsConstructor
public class JwtTokenService implements TokenService {
public class JwtTokenReissueService implements TokenReissueService {

private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final TokenProvider jwtTokenProvider;
private final RefreshTokenRepository refreshTokenRepository;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package com.example.spot.auth.application.refactor.impl;

import com.example.spot.auth.domain.RefreshToken;
import com.example.spot.auth.infrastructure.jpa.RefreshTokenRepository;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO;
import com.example.spot.common.api.code.status.ErrorStatus;
import com.example.spot.common.api.exception.GeneralException;
import com.example.spot.common.security.utils.JwtTokenProvider;
import com.example.spot.auth.application.refactor.TokenProvider;
import com.example.spot.auth.application.refactor.dto.OAuthProfile;
import com.example.spot.auth.application.refactor.dto.SocialAccountResult;
import com.example.spot.auth.application.refactor.member.OAuthMemberConflictProcessor;
import com.example.spot.auth.application.refactor.member.OAuthMemberCreator;
import com.example.spot.auth.application.refactor.member.ProfileCompletenessChecker;
import com.example.spot.auth.application.refactor.member.RefreshTokenStore;
import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO;
import com.example.spot.member.domain.Member;
import com.example.spot.member.domain.enums.LoginType;
import com.example.spot.member.infrastructure.jpa.MemberRepository;
import com.example.spot.member.infrastructure.jpa.PreferredRegionRepository;
import com.example.spot.member.infrastructure.jpa.PreferredThemeRepository;
import com.example.spot.member.infrastructure.jpa.StudyJoinReasonRepository;
import com.example.spot.member.presentation.dto.MemberResponseDTO;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -23,72 +18,29 @@
@RequiredArgsConstructor
public class OAuthMemberProcessor {

private final MemberRepository memberRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final JwtTokenProvider tokenProvider;
private final PreferredThemeRepository preferredThemeRepository;
private final PreferredRegionRepository preferredRegionRepository;
private final StudyJoinReasonRepository studyJoinReasonRepository;
@PersistenceContext
private EntityManager entityManager;
private final OAuthMemberCreator oAuthMemberCreator;
private final OAuthMemberConflictProcessor oAuthMemberConflictProcessor;
private final ProfileCompletenessChecker profileCompletenessChecker;
private final TokenProvider tokenProvider;
private final RefreshTokenStore refreshTokenStore;

@Transactional
public MemberResponseDTO.SocialLoginSignInDTO processOAuthMember(LoginType loginType,
Member providerMember) {
// 다른 로그인 타입으로 가입된 경우
if (memberRepository.existsByEmailAndLoginTypeNot(providerMember.getEmail(), loginType)) {
Member existing = memberRepository.findByEmail(providerMember.getEmail())
.orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND));
if (existing.getInactive() != null) {
refreshTokenRepository.deleteByMemberId(existing.getId());
memberRepository.deleteById(existing.getId());
entityManager.flush();
} else {
throw new GeneralException(ErrorStatus._MEMBER_EMAIL_EXIST);
}
}
public MemberResponseDTO.SocialLoginSignInDTO processOAuthMember(OAuthProfile oAuthProfile) {
SocialAccountResult socialAccountResult = oAuthMemberConflictProcessor.resolveConflict(oAuthProfile);
Member member = socialAccountResult.member().orElseGet(() -> oAuthMemberCreator.createFrom(oAuthProfile));

boolean isSpotMember = false;
Member member = memberRepository.findByEmail(providerMember.getEmail()).orElse(null);
boolean isSpotMember = profileCompletenessChecker.isComplete(member.getId());

if (member != null && member.getInactive() != null) {
refreshTokenRepository.deleteByMemberId(member.getId());
memberRepository.deleteById(member.getId());
entityManager.flush();
member = null;
}
TokenDTO token = tokenProvider.createToken(member.getId());
refreshTokenStore.replace(member.getId(), token.getRefreshToken());

if (member == null) {
member = memberRepository.save(providerMember);
}

isSpotMember = checkIsSpotMember(member);

TokenResponseDTO.TokenDTO token = tokenProvider.createToken(member.getId());
saveRefreshToken(member, token);

return MemberResponseDTO.SocialLoginSignInDTO.toDTO(isSpotMember,
return MemberResponseDTO.SocialLoginSignInDTO.toDTO(
isSpotMember,
MemberResponseDTO.MemberSignInDTO.builder()
.tokens(token)
.memberId(member.getId())
.loginType(member.getLoginType())
.email(member.getEmail())
.build());
}

private void saveRefreshToken(Member member, TokenResponseDTO.TokenDTO token) {
refreshTokenRepository.deleteAllByMemberId(member.getId());
RefreshToken refreshToken = RefreshToken.builder()
.memberId(member.getId())
.token(token.getRefreshToken())
.build();
refreshTokenRepository.save(refreshToken);
}

private boolean checkIsSpotMember(Member member) {
Long id = member.getId();
return preferredThemeRepository.existsByMemberId(id) &&
preferredRegionRepository.existsByMemberId(id) &&
studyJoinReasonRepository.existsByMemberId(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.spot.auth.application.refactor.member;

import com.example.spot.auth.application.refactor.dto.OAuthProfile;
import com.example.spot.auth.application.refactor.dto.SocialAccountResult;
import com.example.spot.auth.infrastructure.jpa.RefreshTokenRepository;
import com.example.spot.common.api.code.status.ErrorStatus;
import com.example.spot.common.api.exception.GeneralException;
import com.example.spot.member.domain.Member;
import com.example.spot.member.infrastructure.jpa.MemberRepository;
import jakarta.persistence.EntityManager;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class OAuthMemberConflictProcessor {

private final MemberRepository memberRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final EntityManager em;

public SocialAccountResult resolveConflict(OAuthProfile p) {
Optional<Member> opt = memberRepository.findByEmail(p.email());
if (opt.isEmpty()) {
return SocialAccountResult.empty();
}

Member existing = opt.get();
// 다른 타입으로 가입된 경우
if (existing.getLoginType() != p.loginType()) {
if (existing.getInactive() != null) {
refreshTokenRepository.deleteByMemberId(existing.getId());
memberRepository.deleteById(existing.getId());
em.flush();

return SocialAccountResult.empty();
}
throw new GeneralException(ErrorStatus._MEMBER_EMAIL_EXIST);
}

// 같은 타입인데 탈퇴 상태면 정리
if (existing.getInactive() != null) {
refreshTokenRepository.deleteByMemberId(existing.getId());
memberRepository.deleteById(existing.getId());
em.flush();
return SocialAccountResult.empty();
}
return SocialAccountResult.of(existing);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.spot.auth.application.refactor.member;

import com.example.spot.auth.application.refactor.dto.OAuthProfile;
import com.example.spot.member.domain.Member;
import com.example.spot.member.infrastructure.jpa.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class OAuthMemberCreator {

private final MemberRepository memberRepository;

public Member createFrom(OAuthProfile oAuthProfile) {
Member member = Member.toMemberByOAuth(oAuthProfile.loginType(), oAuthProfile.nickname(), oAuthProfile.email(),
oAuthProfile.profileImageUrl());
return memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.spot.auth.application.refactor.member;

import com.example.spot.member.infrastructure.jpa.PreferredRegionRepository;
import com.example.spot.member.infrastructure.jpa.PreferredThemeRepository;
import com.example.spot.member.infrastructure.jpa.StudyJoinReasonRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ProfileCompletenessChecker {

private final PreferredThemeRepository themeRepository;
private final PreferredRegionRepository regionRepository;
private final StudyJoinReasonRepository reasonRepository;

public boolean isComplete(Long memberId) {
return themeRepository.existsByMemberId(memberId)
&& regionRepository.existsByMemberId(memberId)
&& reasonRepository.existsByMemberId(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.spot.auth.application.refactor.member;

import com.example.spot.auth.domain.RefreshToken;
import com.example.spot.auth.infrastructure.jpa.RefreshTokenRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class RefreshTokenStore {

private final RefreshTokenRepository refreshTokenRepository;

public void replace(Long memberId, String refresh) {
refreshTokenRepository.deleteAllByMemberId(memberId);
refreshTokenRepository.save(RefreshToken.builder()
.memberId(memberId).
token(refresh)
.build());
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.example.spot.auth.application.refactor.strategy;

import com.example.spot.member.domain.Member;
import com.example.spot.auth.application.refactor.dto.OAuthProfile;
import com.example.spot.member.domain.enums.LoginType;

public interface OAuthStrategy {

LoginType getType();

String getOauthRedirectURL();

Member toMember(String code); // 전략별 구현에서 Member 객체 생성
OAuthProfile getOAuthProfile(String code); // 전략별 구현에서 Member 객체 생성
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,40 @@
import com.example.spot.auth.exception.UnsupportedSocialLoginTypeException;
import com.example.spot.common.api.code.status.ErrorStatus;
import com.example.spot.member.domain.enums.LoginType;
import jakarta.annotation.PostConstruct;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OAuthStrategyFactory {

private final Map<LoginType, OAuthStrategy> strategyMap;

@PostConstruct
void logRegistered() {
log.info("Registered OAuth strategies: {}", strategyMap.keySet());
}

public OAuthStrategyFactory(List<OAuthStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(OAuthStrategy::getType, s -> s));
this.strategyMap = Collections.unmodifiableMap(
strategies.stream().collect(
Collectors.toMap(
OAuthStrategy::getType,
s -> s,
(a, b) -> {
throw new IllegalStateException("중복된 OAuth Strategy가 주입되었습니다. :" + a.getType());
},
() -> new EnumMap<>(LoginType.class)
)
)
);
}

public OAuthStrategy getStrategy(LoginType type) {
Expand Down
Loading