spring.docker.compose.enabled=false
# 콘솔 색깔 보이게 설정
spring.output.ansi.enabled=always
# 1. DB Connection URL
spring.datasource.url=jdbc:postgresql://localhost:5432/maplestory_db
# 2. DB Name, Password
spring.datasource.username=postgres
spring.datasource.password=fje3t2jBQ304s
# 3. PostgreSQL Driver
spring.datasource.driver-class-name=org.postgresql.Driver
# 4. JPA & Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.highlight_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# 5. Nexon API Key
nexon.api.key=test_a14ee3aab189780facbc961d999d503b6e70132855a81231c17caeeb780d4a42efe8d04e6d233bd35cf2fabdeb93fb0d
nexon.api.base-url=https://open.api.nexon.com/maplestory/v1
# 6. Timezone 설정 (한국 서울)
spring.jackson.time-zone=Asia/Seoul
서버용 (아래 내용은 절대 절대 노출되면 안 됩니다.)
spring:
config:
activate:
on-profile: secret
datasource:
url: jdbc:mysql://k12d102.p.ssafy.io/:3306/TAKEN
username: TAKEN
password: ssafyD102!
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
password: ssafyD102!
jwt:
secret: "1e4b6c8d6f1a9ae9bb7472c8d54892fe21d26a396c7f2d5cf7e3a98b1f7a8db3"
access-token-expiration: 86400 # 1일
refresh-token-expiration: 2592000 # 30일 (86400 * 30)
token-prefix: "Bearer "
header-string: "Authorization"
springdoc:
swagger-ui:
path: /api/taken/swagger
url: /api/v3/api-docs
api-docs:
path: /api/v3/api-docs
cloud:
aws:
credentials:
access-key: AKIAZBRTHJF3AUDFXXP7
secret-key: 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
region:
static: ap-northeast-2
auto: false
s3:
bucket: boda-taken-bucket
stack:
auto: false
firebase:
service-account-key-path: /app/fcm/boda.json
kakao:
client-id: "c1e05f5bc2e6c7b109159615df0e3b34" # 카카오 REST API 키
redirect-uri: "<https://k12d102.p.ssafy.io/api/oauth/kakao>" # 카카오 로그인 완료 후 리다이렉트될 URI
kakao-map:
api-key: "KakaoAK 67d5193c667f375e6698e2078ec62d1d"
로컬용
spring:
config:
activate:
on-profile: secret
datasource:
url: jdbc:mysql://localhost:3306/[데이터베이스명]
username: [mysql 유저명]
password: [mysql 비밀번호]
driver-class-name: com.mysql.cj.jdbc.Driver
jwt:
secret: "1e4b6c8d6f1a9ae9bb7472c8d54892fe21d26a396c7f2d5cf7e3a98b1f7a8db3"
access-token-expiration: 86400 # 1일
refresh-token-expiration: 2592000 # 30일 (86400 * 30)
token-prefix: "Bearer "
header-string: "Authorization"
springdoc:
swagger-ui:
path: /api/taken/swagger
url: /api/v3/api-docs
api-docs:
path: /api/v3/api-docs
cloud:
aws:
credentials:
access-key: AKIAZBRTHJF3AUDFXXP7
secret-key: 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
region:
static: ap-northeast-2
auto: false
s3:
bucket: boda-taken-bucket
stack:
auto: false
firebase:
service-account-key-path: ./src/main/resources/fcm/boda-90475-firebase-adminsdk-fbsvc-df2dcd8c1e.json
kakao:
client-id: "c1e05f5bc2e6c7b109159615df0e3b34" # 카카오 REST API 키
redirect-uri: "<http://localhost:5173/oauth/kakao>" # 카카오 로그인 완료 후 리다이렉트될 URI
kakao-map:
api-key: "KakaoAK 67d5193c667f375e6698e2078ec62d1d"
public String getKakaoToken(String code) {
try {
log.info("카카오 토큰 요청 시작. code: {}", code);
String tokenUri = KAKAO_TOKEN_URI;
// 중요: application.yml에서 주입받은 kakaoRedirectUri가 네이티브 앱용인지 확인 필요!
// 네이티브 앱의 경우 보통 "kakao{네이티브_앱_키}://oauth" 형식이거나, 이 파라미터를 보내지 않음.
String redirectUri = kakaoRedirectUri;
log.info("--- 카카오 액세스 토큰 요청 파라미터 ---");
log.info("요청 URL: {}", tokenUri);
log.info("grant_type: authorization_code");
log.info("client_id: {}", kakaoClientId);
log.info("redirect_uri: {}", redirectUri); // 실제 사용될 redirect_uri 값 로깅
log.info("code: {}", code);
log.info("------------------------------------");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", kakaoClientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
try {
log.info("카카오 액세스 토큰 요청 헤더 (전송 전): {}", objectMapper.writeValueAsString(requestEntity.getHeaders()));
log.info("카카오 액세스 토큰 요청 바디 (전송 전): {}", objectMapper.writeValueAsString(requestEntity.getBody()));
} catch (JsonProcessingException e) {
log.warn("카카오 토큰 요청 정보 로깅(JSON 변환) 실패", e);
}
log.info("카카오 액세스 토큰 POST 요청 전송 시작: {}", tokenUri);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity;
try {
responseEntity = restTemplate.postForEntity(tokenUri, requestEntity, String.class);
log.info("--- 카카오 액세스 토큰 응답 (성공) ---");
log.info("HTTP Status: {}", responseEntity.getStatusCode());
log.info("Response Headers: {}", responseEntity.getHeaders());
log.info("Response Body: {}", responseEntity.getBody());
log.info("------------------------------------");
JsonNode responseJson = objectMapper.readTree(responseEntity.getBody());
String accessToken = responseJson.path("access_token").asText(null);
if (accessToken == null) {
log.error("카카오 응답에서 access_token을 찾을 수 없습니다. 응답 본문: {}", responseEntity.getBody());
throw new RuntimeException("카카오 응답에서 access_token을 찾을 수 없습니다.");
}
log.info("카카오 액세스 토큰 추출 성공. Token Length: {}", accessToken.length());
return accessToken;
} catch (HttpClientErrorException | HttpServerErrorException e) {
log.error("--- 카카오 액세스 토큰 요청 실패 (HTTP 오류) ---");
log.error("HTTP Status: {}", e.getStatusCode());
log.error("Response Headers: {}", e.getResponseHeaders());
log.error("Response Body: {}", e.getResponseBodyAsString(), e);
log.error("---------------------------------------------");
throw new RuntimeException("카카오 토큰 요청 중 HTTP 오류 발생: " + e.getStatusCode() + " - " + e.getResponseBodyAsString());
} catch (Exception e) { // JSON 파싱 오류 등 RestTemplate 호출 후의 다른 예외
log.error("카카오 토큰 응답 처리 중 예외 발생.", e);
throw new RuntimeException("카카오 토큰 응답 처리 중 예외가 발생했습니다.", e);
}
} catch (Exception e) { // getKakaoToken 메소드 전체를 감싸는 최상위 예외 처리
log.error("getKakaoToken 메소드 실행 중 최종 오류 발생.", e);
throw new RuntimeException("카카오 토큰을 가져오는 과정에서 심각한 오류가 발생했습니다.", e);
}
}
public LoginResponseDto kakaoLogin(String code) throws IOException {
log.info("카카오 로그인 로직 시작. Authorization Code: {}", code);
try {
String accessToken = getKakaoToken(code);
if (!StringUtils.hasText(accessToken)) {
log.error("getKakaoToken으로부터 유효한 액세스 토큰을 받지 못했습니다.");
throw new RuntimeException("카카오 액세스 토큰을 얻지 못했습니다.");
}
log.info("카카오 액세스 토큰 성공적으로 획득. Token Length: {}", accessToken.length());
String memberInfoUri = KAKAO_USER_INFO_URI;
HttpHeaders userInfoHeaders = new HttpHeaders();
userInfoHeaders.setBearerAuth(accessToken);
// Kakao API v2 사용자 정보 요청 시 Content-type 헤더는 필수가 아님 (보통 GET 요청)
// userInfoHeaders.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
HttpEntity<?> userInfoRequestEntity = new HttpEntity<>(userInfoHeaders);
log.info("--- 카카오 사용자 정보 요청 준비 ---");
log.info("요청 URL: {}", memberInfoUri);
try {
log.info("요청 Headers: {}", objectMapper.writeValueAsString(userInfoRequestEntity.getHeaders()));
} catch (JsonProcessingException e) {
log.warn("카카오 사용자 정보 요청 헤더 로깅(JSON 변환) 실패", e);
}
log.info("카카오 사용자 정보 GET 요청 전송 시작");
log.info("----------------------------------");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> userInfoResponseEntity;
try {
userInfoResponseEntity = restTemplate.exchange(
memberInfoUri,
HttpMethod.GET,
userInfoRequestEntity,
String.class
);
log.info("--- 카카오 사용자 정보 응답 (성공) ---");
log.info("HTTP Status: {}", userInfoResponseEntity.getStatusCode());
log.info("Response Headers: {}", userInfoResponseEntity.getHeaders());
log.info("Response Body: {}", userInfoResponseEntity.getBody());
log.info("------------------------------------");
} catch (HttpClientErrorException | HttpServerErrorException e) {
log.error("--- 카카오 사용자 정보 요청 실패 (HTTP 오류) ---");
log.error("HTTP Status: {}", e.getStatusCode());
log.error("Response Headers: {}", e.getResponseHeaders());
log.error("Response Body: {}", e.getResponseBodyAsString(), e);
log.error("---------------------------------------------");
throw new RuntimeException("카카오 사용자 정보 요청 중 HTTP 오류 발생: " + e.getStatusCode() + " - " + e.getResponseBodyAsString());
} catch (Exception e) { // JSON 파싱 오류 등 RestTemplate 호출 후의 다른 예외
log.error("카카오 사용자 정보 응답 처리 중 예외 발생.", e);
throw new RuntimeException("카카오 사용자 정보 응답 처리 중 예외가 발생했습니다.", e);
}
JsonNode json = objectMapper.readTree(userInfoResponseEntity.getBody());
Long kakaoId = json.get("id").asLong();
// 카카오 정책에 따라 이메일 제공 여부 및 기본값 처리 필요
String email = json.path("kakao_account").path("email").asText(null);
if (email == null) { // 이메일 동의 안했거나 없는 경우 대체 이메일 생성
email = "kakao_" + kakaoId + "@boda.com"; // 서비스 정책에 맞게 수정
log.warn("카카오로부터 이메일 정보를 받지 못했습니다. 대체 이메일 사용: {}", email);
}
String nickname = json.path("kakao_account").path("profile").path("nickname").asText("BODA 사용자");
log.info("파싱된 카카오 사용자 정보 - ID: {}, Email: {}, Nickname: {}", kakaoId, email, nickname);
String finalEmail = email;
Member member = memberRepository.findByProviderAndProviderId("kakao", kakaoId)
.orElseGet(() -> {
log.info("새로운 카카오 사용자 등록 시도. KakaoID: {}", kakaoId);
String profileImageUrl = json.path("kakao_account").path("profile").path("profile_image_url").asText(DEFAULT_PROFILE_IMAGE_URL);
Member newMember = Member.builder()
.email(finalEmail)
.nickname(nickname)
// .password(null) // 소셜 로그인이므로 비밀번호는 null
// .phoneNo(null) // 필요 시 추가 수집
.points(0)
.profile(profileImageUrl)
.provider("kakao")
.providerId(kakaoId)
.build();
return memberRepository.save(newMember);
});
log.info("기존 또는 신규 카카오 회원 처리 완료. MemberID: {}", member.getMemberId());
TokenResponseDto tokens = jwtService.issueToken(member);
log.info("자체 JWT 토큰 발급 성공. MemberID: {}", member.getMemberId());
return LoginResponseDto.builder()
.email(member.getEmail())
.nickname(member.getNickname())
.address(member.getAddress())
.points(member.getPoints())
.profile(member.getProfile())
.accessToken(tokens.getAccessToken())
.refreshToken(tokens.getRefreshToken())
.build();
} catch (IOException e) {
log.error("카카오 로그인 중 데이터 처리(I/O) 오류 발생.", e);
throw new RuntimeException("카카오 로그인 데이터 처리 중 오류가 발생했습니다.", e);
} catch (RuntimeException e) {
log.error("카카오 로그인 중 런타임 예외 발생: {}", e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("카카오 로그인 중 예기치 못한 최종 오류 발생.", e);
throw new RuntimeException("카카오 로그인 처리 중 예기치 못한 최종 오류가 발생했습니다.", e);
}
}