Skip to content

Commit 04ca166

Browse files
authored
Merge pull request #337 from asowjdan/fix/member
Fix[member]:토큰 생성 로직 수정으로 인한 테스트 코드 수정
2 parents 2ced5c0 + ba09634 commit 04ca166

File tree

8 files changed

+222
-113
lines changed

8 files changed

+222
-113
lines changed

backend/src/main/java/com/ai/lawyer/domain/member/repositories/MemberRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import java.util.List;
88
import java.util.Optional;
99

10+
/**
11+
* MemberRepository
12+
* findById 호출 시 SmartMemberRepositoryImpl을 통해 AuthUtil로 자동 리다이렉트됩니다.
13+
* 이를 통해 loginType에 따라 Member 또는 OAuth2Member 테이블에서 조회합니다.
14+
*/
1015
@Repository
1116
public interface MemberRepository extends JpaRepository<Member, Long> {
1217

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.ai.lawyer.domain.member.repositories;
2+
3+
import com.ai.lawyer.domain.member.entity.Member;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
7+
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
8+
import org.springframework.data.repository.core.RepositoryMetadata;
9+
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
10+
11+
import jakarta.persistence.EntityManager;
12+
import java.io.Serializable;
13+
14+
/**
15+
* MemberRepository의 커스텀 팩토리 빈
16+
* findById 호출을 가로채서 AuthUtil을 통해 처리하도록 합니다.
17+
*/
18+
public class MemberRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
19+
extends JpaRepositoryFactoryBean<R, T, I> {
20+
21+
public MemberRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
22+
super(repositoryInterface);
23+
}
24+
25+
@NotNull
26+
@Override
27+
protected RepositoryFactorySupport createRepositoryFactory(@NotNull EntityManager entityManager) {
28+
return new MemberRepositoryFactory(entityManager);
29+
}
30+
31+
private static class MemberRepositoryFactory extends JpaRepositoryFactory {
32+
33+
public MemberRepositoryFactory(EntityManager entityManager) {
34+
super(entityManager);
35+
}
36+
37+
@NotNull
38+
@Override
39+
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
40+
// Member 엔티티인 경우 커스텀 베이스 클래스 사용
41+
if (Member.class.isAssignableFrom(metadata.getDomainType())) {
42+
return SmartMemberRepositoryImpl.class;
43+
}
44+
return super.getRepositoryBaseClass(metadata);
45+
}
46+
}
47+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.ai.lawyer.domain.member.repositories;
2+
3+
import com.ai.lawyer.domain.member.entity.Member;
4+
import com.ai.lawyer.global.util.AuthUtil;
5+
import org.jetbrains.annotations.NotNull;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
9+
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
10+
import org.springframework.web.server.ResponseStatusException;
11+
12+
import jakarta.persistence.EntityManager;
13+
import java.util.Optional;
14+
15+
/**
16+
* MemberRepository의 커스텀 베이스 구현체
17+
* findById 호출 시 AuthUtil을 통해 적절한 테이블에서 조회합니다.
18+
*/
19+
public class SmartMemberRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> {
20+
21+
private static final Logger log = LoggerFactory.getLogger(SmartMemberRepositoryImpl.class);
22+
23+
private final JpaEntityInformation<T, ?> entityInformation;
24+
25+
public SmartMemberRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
26+
EntityManager entityManager) {
27+
super(entityInformation, entityManager);
28+
this.entityInformation = entityInformation;
29+
}
30+
31+
@NotNull
32+
@Override
33+
public Optional<T> findById(@NotNull ID id) {
34+
// Member 엔티티이고 ID가 Long인 경우에만 AuthUtil 사용
35+
if (entityInformation.getJavaType().equals(Member.class) && id instanceof Long) {
36+
try {
37+
log.debug("SmartMemberRepositoryImpl.findById 호출: memberId={}", id);
38+
Member member = AuthUtil.getMemberOrThrow((Long) id);
39+
@SuppressWarnings("unchecked")
40+
T result = (T) member;
41+
return Optional.of(result);
42+
} catch (ResponseStatusException e) {
43+
log.debug("회원을 찾을 수 없음: memberId={}", id);
44+
return Optional.empty();
45+
}
46+
}
47+
48+
// 다른 엔티티는 기본 동작 수행
49+
return super.findById(id);
50+
}
51+
}

backend/src/main/java/com/ai/lawyer/global/config/DataDBConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ai.lawyer.global.config;
22

3+
import com.ai.lawyer.domain.member.repositories.MemberRepositoryFactoryBean;
34
import org.springframework.beans.factory.annotation.Value;
45
import org.springframework.boot.jdbc.DataSourceBuilder;
56
import org.springframework.context.annotation.Bean;
@@ -18,7 +19,8 @@
1819
@EnableJpaRepositories(
1920
basePackages = "com.ai.lawyer.domain.*",
2021
entityManagerFactoryRef = "dataEntityManager",
21-
transactionManagerRef = "dataTransactionManager"
22+
transactionManagerRef = "dataTransactionManager",
23+
repositoryFactoryBeanClass = MemberRepositoryFactoryBean.class
2224
)
2325
public class DataDBConfig {
2426

@@ -49,7 +51,7 @@ public LocalContainerEntityManagerFactoryBean dataEntityManager() {
4951
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
5052

5153
em.setDataSource(dataDBSource());
52-
em.setPackagesToScan(new String[]{"com.ai.lawyer.domain.*"});
54+
em.setPackagesToScan("com.ai.lawyer.domain.*");
5355
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
5456

5557
HashMap<String, Object> properties = new HashMap<>();

backend/src/main/java/com/ai/lawyer/global/qdrant/initializer/QdrantInitializer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import lombok.RequiredArgsConstructor;
66
import lombok.extern.slf4j.Slf4j;
77
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.context.annotation.Profile;
89
import org.springframework.stereotype.Component;
910

1011
import java.util.concurrent.ExecutionException;
1112

1213
@Slf4j
1314
@Component
1415
@RequiredArgsConstructor
16+
@Profile("!test") // test 프로파일에서는 비활성화
1517
public class QdrantInitializer {
1618

1719
private final QdrantClient qdrantClient;

backend/src/main/java/com/ai/lawyer/global/security/SecurityConfig.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ public class SecurityConfig {
5656
"/api/precedent/**", // 판례 (공개)
5757
"/api/law/**", // 법령 (공개)
5858
"/api/law-word/**", // 법률 용어 (공개)
59-
"/api/chat/**", // 챗봇 (공개)
6059
"/api/home/**", // 홈 (공개)
6160
"/h2-console/**", // H2 콘솔 (개발용)
6261
"/actuator/health", "/actuator/health/**", "/actuator/info", // Spring Actuator

backend/src/main/java/com/ai/lawyer/global/util/AuthUtil.java

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
package com.ai.lawyer.global.util;
22

3+
import jakarta.persistence.EntityManager;
34
import org.springframework.security.core.Authentication;
45
import org.springframework.security.core.GrantedAuthority;
56
import org.springframework.security.core.context.SecurityContextHolder;
67
import org.springframework.web.server.ResponseStatusException;
78
import org.springframework.http.HttpStatus;
89
import org.springframework.stereotype.Component;
910
import org.springframework.beans.factory.annotation.Autowired;
10-
import com.ai.lawyer.domain.member.repositories.MemberRepository;
11-
import com.ai.lawyer.domain.member.repositories.OAuth2MemberRepository;
1211
import com.ai.lawyer.domain.member.entity.Member;
1312

1413
@Component
1514
public class AuthUtil {
16-
private static MemberRepository memberRepository;
17-
private static OAuth2MemberRepository oauth2MemberRepository;
15+
private static EntityManager entityManager;
1816

1917
@Autowired
20-
public AuthUtil(MemberRepository memberRepository) {
21-
AuthUtil.memberRepository = memberRepository;
22-
}
23-
24-
@Autowired(required = false)
25-
public void setOauth2MemberRepository(OAuth2MemberRepository oauth2MemberRepository) {
26-
AuthUtil.oauth2MemberRepository = oauth2MemberRepository;
18+
public AuthUtil(EntityManager entityManager) {
19+
AuthUtil.entityManager = entityManager;
2720
}
2821

2922
public static Long getCurrentMemberId() {
@@ -67,37 +60,65 @@ public static String getCurrentMemberRole() {
6760
.orElse(null);
6861
}
6962

63+
/**
64+
* 현재 인증된 사용자의 로그인 타입을 가져옵니다.
65+
* @return "LOCAL" 또는 "OAUTH2", 없으면 null
66+
*/
67+
public static String getCurrentLoginType() {
68+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
69+
if (authentication == null || !authentication.isAuthenticated()) {
70+
return null;
71+
}
72+
73+
Object details = authentication.getDetails();
74+
if (details instanceof java.util.Map) {
75+
@SuppressWarnings("unchecked")
76+
java.util.Map<String, String> detailsMap = (java.util.Map<String, String>) details;
77+
return detailsMap.get("loginType");
78+
}
79+
80+
return null;
81+
}
82+
7083
/**
7184
* memberId로 회원을 조회합니다. (Member 또는 OAuth2Member)
85+
* SecurityContext에서 현재 인증된 사용자의 loginType을 자동으로 확인하여 적절한 테이블에서 조회합니다.
7286
* OAuth2Member인 경우 Member 객체로 변환하여 반환합니다.
87+
* EntityManager를 직접 사용하여 무한 루프를 방지합니다.
7388
* @param memberId 회원 ID
7489
* @return Member 객체
7590
* @throws ResponseStatusException 회원을 찾을 수 없는 경우
7691
*/
7792
public static Member getMemberOrThrow(Long memberId) {
78-
// 먼저 Member 테이블에서 조회
79-
java.util.Optional<Member> member = memberRepository.findById(memberId);
80-
if (member.isPresent()) {
81-
return member.get();
93+
// SecurityContext에서 loginType 자동 추출
94+
String loginType = getCurrentLoginType();
95+
96+
// loginType이 있으면 해당 테이블에서만 조회 (성능 최적화)
97+
if (loginType != null) {
98+
return getMemberOrThrow(memberId, loginType);
8299
}
83100

84-
// Member 테이블에 없으면 OAuth2Member 테이블에서 조회
85-
if (oauth2MemberRepository != null) {
86-
java.util.Optional<com.ai.lawyer.domain.member.entity.OAuth2Member> oauth2Member =
87-
oauth2MemberRepository.findById(memberId);
88-
if (oauth2Member.isPresent()) {
89-
// OAuth2Member를 Member로 변환 (엔티티 호환성을 위해)
90-
com.ai.lawyer.domain.member.entity.OAuth2Member oauth = oauth2Member.get();
91-
return Member.builder()
92-
.memberId(oauth.getMemberId())
93-
.loginId(oauth.getLoginId())
94-
.name(oauth.getName())
95-
.age(oauth.getAge())
96-
.gender(oauth.getGender())
97-
.role(oauth.getRole())
98-
.password("") // OAuth2는 비밀번호 없음
99-
.build();
100-
}
101+
// loginType이 없으면 하위 호환성을 위해 두 테이블 모두 조회
102+
// 먼저 Member 테이블에서 조회 (EntityManager 직접 사용)
103+
Member member = entityManager.find(Member.class, memberId);
104+
if (member != null) {
105+
return member;
106+
}
107+
108+
// Member 테이블에 없으면 OAuth2Member 테이블에서 조회 (EntityManager 직접 사용)
109+
com.ai.lawyer.domain.member.entity.OAuth2Member oauth2Member =
110+
entityManager.find(com.ai.lawyer.domain.member.entity.OAuth2Member.class, memberId);
111+
if (oauth2Member != null) {
112+
// OAuth2Member를 Member로 변환 (엔티티 호환성을 위해)
113+
return Member.builder()
114+
.memberId(oauth2Member.getMemberId())
115+
.loginId(oauth2Member.getLoginId())
116+
.name(oauth2Member.getName())
117+
.age(oauth2Member.getAge())
118+
.gender(oauth2Member.getGender())
119+
.role(oauth2Member.getRole())
120+
.password("") // OAuth2는 비밀번호 없음
121+
.build();
101122
}
102123

103124
// 둘 다 없으면 예외 발생
@@ -107,36 +128,35 @@ public static Member getMemberOrThrow(Long memberId) {
107128
/**
108129
* memberId와 loginType으로 회원을 조회합니다.
109130
* loginType이 "LOCAL"이면 Member 테이블에서, "OAUTH2"이면 OAuth2Member 테이블에서 조회합니다.
131+
* EntityManager를 직접 사용하여 무한 루프를 방지합니다.
110132
* @param memberId 회원 ID
111133
* @param loginType 로그인 타입 ("LOCAL" 또는 "OAUTH2")
112134
* @return Member 객체
113135
* @throws ResponseStatusException 회원을 찾을 수 없는 경우
114136
*/
115137
public static Member getMemberOrThrow(Long memberId, String loginType) {
116138
if ("OAUTH2".equals(loginType)) {
117-
// OAuth2 회원 조회
118-
if (oauth2MemberRepository != null) {
119-
java.util.Optional<com.ai.lawyer.domain.member.entity.OAuth2Member> oauth2Member =
120-
oauth2MemberRepository.findById(memberId);
121-
if (oauth2Member.isPresent()) {
122-
// OAuth2Member를 Member로 변환
123-
com.ai.lawyer.domain.member.entity.OAuth2Member oauth = oauth2Member.get();
124-
return Member.builder()
125-
.memberId(oauth.getMemberId())
126-
.loginId(oauth.getLoginId())
127-
.name(oauth.getName())
128-
.age(oauth.getAge())
129-
.gender(oauth.getGender())
130-
.role(oauth.getRole())
131-
.password("") // OAuth2는 비밀번호 없음
132-
.build();
133-
}
139+
// OAuth2 회원 조회 (EntityManager 직접 사용)
140+
com.ai.lawyer.domain.member.entity.OAuth2Member oauth2Member =
141+
entityManager.find(com.ai.lawyer.domain.member.entity.OAuth2Member.class, memberId);
142+
143+
if (oauth2Member != null) {
144+
// OAuth2Member를 Member로 변환
145+
return Member.builder()
146+
.memberId(oauth2Member.getMemberId())
147+
.loginId(oauth2Member.getLoginId())
148+
.name(oauth2Member.getName())
149+
.age(oauth2Member.getAge())
150+
.gender(oauth2Member.getGender())
151+
.role(oauth2Member.getRole())
152+
.password("") // OAuth2는 비밀번호 없음
153+
.build();
134154
}
135155
} else {
136-
// LOCAL 회원 조회 (기본값)
137-
java.util.Optional<Member> member = memberRepository.findById(memberId);
138-
if (member.isPresent()) {
139-
return member.get();
156+
// LOCAL 회원 조회 (EntityManager 직접 사용)
157+
Member member = entityManager.find(Member.class, memberId);
158+
if (member != null) {
159+
return member;
140160
}
141161
}
142162

0 commit comments

Comments
 (0)