Skip to content
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
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# ====================================
# 민감 정보 파일 (절대 커밋 금지!)
# ====================================

# 환경별 설정 파일
application-local.yml
application-prod.yml
application-prod-debug.yml

# 환경 변수 파일
.env
.env.local
.env.production
.env.*.local

# 테스트 파일
test.html
*.test.html

# ====================================
# 기존 .gitignore 내용
# ====================================

HELP.md
.gradle
build/
Expand Down Expand Up @@ -35,3 +58,19 @@ out/

### VS Code ###
.vscode/

### Mac ###
.DS_Store

### Windows ###
Thumbs.db

### Logs ###
*.log
logs/

### Temporary files ###
*.tmp
*.bak
*.swp
*~.nib
2 changes: 1 addition & 1 deletion api-test.http
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### 전역 변수 설정 (필요시 변경)
@auth_host = http://localhost:8080
@auth_host = http://15.164.112.64:8081
@trip_host = http://localhost:8081
@email = test@naver.com
@password = password1234
Expand Down
6 changes: 3 additions & 3 deletions api-test2.http
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

### 전역 변수 설정 (랜덤 값 사용)
@random_id = {{$randomInt}}
@email = test{{random_id}}@naver.com
@email = test2{{random_id}}@naver.com
@password = password1234
@new_password = newpassword5678
@auth_host = http://localhost:8080
@trip_host = http://localhost:8081
@auth_host = http://15.164.112.64:8081
@trip_host = http://15.164.112.64:8080

### ========================================
### 📌 Phase 1: 회원가입 및 토큰 발급 검증
Expand Down
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ dependencies {
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'

implementation 'com.mysql:mysql-connector-j'
// OkHttp (포트원 API 호출용)
implementation 'com.squareup.okhttp3:okhttp:4.12.0'

implementation('com.google.code.gson:gson') {
version {
strictly '2.11.0'
}
}
}

tasks.named('test') {
Expand Down
32 changes: 15 additions & 17 deletions src/main/java/com/retrip/auth/application/config/JwtProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ public class JwtProvider {

private final JwtConfig jwtConfig;

// ... createToken, generateTokens 등 생성 로직은 기존 유지 ...
// (위에 작성하신 코드 그대로 두셔도 됩니다. 아래 getAuthentication만 수정하면 됩니다.)

public LoginResponse.TokenResponse generateTokens(Authentication authentication) {
Instant now = Instant.now();
String authorities = String.join(",", getAuthorities(authentication));
Expand All @@ -45,13 +42,12 @@ public LoginResponse.TokenResponse generateTokens(Authentication authentication)

Object principal = authentication.getPrincipal();
if (principal instanceof CustomUserDetails userDetails) {
memberId = userDetails.getName(); // UUID
memberId = userDetails.getName();
email = userDetails.getEmail();
name = userDetails.getRealName();
gender = userDetails.getGender();
age = userDetails.getAge();
} else {
// principal이 String인 경우 (방어 코드)
memberId = authentication.getName();
}

Expand All @@ -71,7 +67,7 @@ private String createToken(String subject, String email, String name, String gen
.subject(subject)
.claim("username", email)
.claim("name", name)
.claim("authorities", authorities);
.claim("authorities", authorities); // 권한이 없으면 빈 문자열 ""이 들어감

if (gender != null) builder.claim("gender", gender);
if (age != null) builder.claim("age", age);
Expand All @@ -86,7 +82,6 @@ private String createToken(String subject, String email, String name, String gen
}
}

// [중요 수정] Authentication 객체 생성 시 CustomUserDetails 재구성
public Authentication getAuthentication(String token) {
try {
PublicKey publicKey = getPublicKey(jwtConfig.getPublicKey());
Expand All @@ -97,33 +92,36 @@ public Authentication getAuthentication(String token) {
.getPayload();

// 1. Claims에서 정보 추출
String memberId = claims.getSubject(); // UUID
String memberId = claims.getSubject();
String email = claims.get("username", String.class);
String name = claims.get("name", String.class);
String authoritiesStr = claims.get("authorities", String.class);
String gender = claims.get("gender", String.class);
Integer age = claims.get("age", Integer.class);

// 2. 권한 목록 생성
List<GrantedAuthority> authorities = Arrays.stream(authoritiesStr.split(","))
.map(String::trim)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

// 3. 임시 Member 객체 생성 (비밀번호는 null 처리)
// 2. 권한 목록 생성 [수정된 부분: 빈 문자열 처리 추가]
List<GrantedAuthority> authorities = new ArrayList<>();
if (authoritiesStr != null && !authoritiesStr.isBlank()) {
authorities = Arrays.stream(authoritiesStr.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty()) // 빈 문자열 필터링 (중요)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}

// 3. 임시 Member 객체 생성
Member member = Member.builder()
.id(UUID.fromString(memberId))
.email(new MemberEmail(email))
.name(new MemberName(name))
.gender(gender)
.age(age)
.password(null) // 인증된 상태이므로 비밀번호 불필요
.password(null)
.build();

// 4. CustomUserDetails 생성
CustomUserDetails principal = new CustomUserDetails(member);

// 5. Authentication 리턴 (이제 Principal은 CustomUserDetails임)
return new UsernamePasswordAuthenticationToken(principal, token, authorities);

} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public LoginAuthenticationFilter loginAuthenticationFilter(
JwtConfig jwtConfig,
AuthenticationManager authenticationManager,
JwtProvider jwtProvider) {
// 생성자에 refreshTokenRepository가 포함되어야 합니다.
LoginAuthenticationFilter filter = new LoginAuthenticationFilter(jwtConfig, authenticationManager, jwtProvider, refreshTokenRepository);
return filter;
}
Expand All @@ -77,8 +76,6 @@ public SecurityFilterChain securityFilterChain(
HttpSecurity http,
LoginAuthenticationFilter loginAuthenticationFilter) throws Exception {

// [핵심] 401 에러 해결을 위한 SecurityContextRepository 설정
// 이 부분이 빠져있어서 인증 정보가 유지되지 않았습니다.
SecurityContextRepository securityContextRepository = new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
Expand All @@ -90,7 +87,6 @@ public SecurityFilterChain securityFilterChain(
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())

// [핵심] SecurityContext 설정 추가
.securityContext(context -> context
.securityContextRepository(securityContextRepository)
)
Expand All @@ -113,10 +109,14 @@ public SecurityFilterChain securityFilterChain(
)

.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/users").permitAll()
// ✅ 수정: /api/users 경로 추가
.requestMatchers(HttpMethod.POST, "/users", "/api/users").permitAll()
.requestMatchers("/login/**", "/oauth2/**", "/auth/reissue", "/auth/logout", "/").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll()
.requestMatchers("/debug/**").permitAll()
// ✅ 추가: 본인인증 및 여행 스타일 조회 API 허용
.requestMatchers(HttpMethod.GET, "/api/travel-styles").permitAll()
.requestMatchers(HttpMethod.POST, "/api/auth/verify-identity").authenticated()
.anyRequest().authenticated()
);

Expand All @@ -126,14 +126,21 @@ public SecurityFilterChain securityFilterChain(
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));

// 개발 환경: 모든 localhost 포트 허용
config.setAllowedOriginPatterns(List.of(
"http://localhost:*",
"http://127.0.0.1:*"
));

config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setExposedHeaders(List.of("Authorization"));
config.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.retrip.auth.application.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CertificationInfo {
private String name; // 이름
private String gender; // 성별 (male/female)
private String birthday; // 생년월일 (YYYYMMDD)
private String uniqueKey; // CI
private String uniqueInSite; // DI
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.retrip.auth.application.dto.request;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UpdateNotificationRequest {

@NotNull(message = "알림 설정 값은 필수입니다.")
private Boolean enabled;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.retrip.auth.application.dto.request;

import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
public class UpdateProfileRequest {

@Size(max = 30, message = "소개는 최대 30자까지 입력 가능합니다.")
private String bio;

@Size(max = 4, message = "MBTI는 4자리여야 합니다.")
private String mbti;

private String profileImageUrl;

@Size(max = 3, message = "여행 스타일은 최대 3개까지 선택 가능합니다.")
private List<String> travelStyles;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.retrip.auth.application.dto.request;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class VerifyIdentityRequest {

@NotBlank(message = "imp_uid는 필수입니다.")
private String impUid;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.retrip.auth.application.dto.response;

import com.retrip.auth.domain.entity.Member;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class ProfileResponse {
private String email;
private String name;
private String gender;
private Integer age;
private Boolean isVerified;
private String profileImageUrl;
private String bio;
private String mbti;
private List<String> travelStyles;
private Boolean notificationEnabled;

public static ProfileResponse from(Member member, List<String> travelStyles) {
return ProfileResponse.builder()
.email(member.getEmailValue())
.name(member.getNameValue())
.gender(member.getGender())
.age(member.getAge())
.isVerified(member.isVerified())
.profileImageUrl(member.getProfileImageUrl())
.bio(member.getBio())
.mbti(member.getMbti())
.travelStyles(travelStyles)
.notificationEnabled(member.isNotificationEnabled())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.retrip.auth.application.dto.response;

import com.retrip.auth.domain.entity.Member;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class VerifyIdentityResponse {
private String name;
private String gender;
private Integer age;
private Boolean isVerified;

public static VerifyIdentityResponse from(Member member) {
return VerifyIdentityResponse.builder()
.name(member.getNameValue())
.gender(member.getGender())
.age(member.getAge())
.isVerified(member.isVerified())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@

package com.retrip.auth.application.out.repository;

import com.retrip.auth.domain.entity.Member;
import com.retrip.auth.domain.vo.MemberEmail;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface MemberRepository extends JpaRepository<Member, UUID> {
List<Member> findByEmailAndIsDeletedFalse(MemberEmail email);
List<Member> findByEmail(MemberEmail email);
}

// 추가: 이메일로 단건 조회 (Optional)
Optional<Member> findByEmail_Value(String email);

// 추가: CI 중복 체크
boolean existsByCi(String ci);
}
Loading