-
Notifications
You must be signed in to change notification settings - Fork 1
feat: userId 카카오 로그인에서 추출 #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1177271
473dde2
64b3bb4
55d4870
c9d5839
0884c30
f15001a
6a7de02
834da0e
0c63207
15de794
8388b23
a5211f1
dd82c55
cfc8b9f
8e8e179
41cf4bb
9d3a03f
e0b06ed
53c941d
91c8e6d
dab0be3
e5fe0f9
697b912
1325389
9b1d5ee
7a807d2
5f4861f
d5ca147
2fb875a
97821a9
5b07aeb
5febf7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -107,13 +107,13 @@ jobs: | |||||||||||||||||||||||||
| # 환경변수 파일 업데이트 | ||||||||||||||||||||||||||
| cat > .env << EOF | ||||||||||||||||||||||||||
| DB_HOST=${{ secrets.DB_HOST }} | ||||||||||||||||||||||||||
| DB_NAME=db-wisecard | ||||||||||||||||||||||||||
| DB_NAME=wisecarddb | ||||||||||||||||||||||||||
| DB_USERNAME=admin | ||||||||||||||||||||||||||
| DB_PASSWORD=${{ secrets.DB_PASSWORD }} | ||||||||||||||||||||||||||
| DB_PASSWORD=wisecard | ||||||||||||||||||||||||||
| DB_PORT=3306 | ||||||||||||||||||||||||||
| REDIS_HOST=172.17.0.1 | ||||||||||||||||||||||||||
| REDIS_HOST=redis | ||||||||||||||||||||||||||
| REDIS_PORT=6379 | ||||||||||||||||||||||||||
| REDIS_PASSWORD=1234 | ||||||||||||||||||||||||||
| REDIS_PASSWORD= | ||||||||||||||||||||||||||
|
Comment on lines
+112
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 환경 변수에 비밀번호를 하드코딩하지 마세요.
- DB_PASSWORD=wisecard
+ DB_PASSWORD=${{ secrets.DB_PASSWORD }}
...
- REDIS_PASSWORD=
+ REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| APP_API_KAKAO_KEY=${{ secrets.APP_API_KAKAO_KEY }} | ||||||||||||||||||||||||||
| APP_PORT=8080 | ||||||||||||||||||||||||||
| GRPC_PORT=9091 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,9 @@ dependencies { | |
| implementation 'org.springframework.boot:spring-boot-starter-data-redis' | ||
| implementation 'org.springframework.boot:spring-boot-starter-cache' | ||
|
|
||
| implementation 'me.paulschwarz:spring-dotenv:4.0.0' // .env 읽기 | ||
| // implementation 'me.paulschwarz:spring-dotenv:4.0.0' // .env 읽기 | ||
|
|
||
| implementation 'io.github.cdimascio:dotenv-java:2.2.0' // 최신 안정화 버전으로 변경하세요 | ||
|
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chaindotenv 라이브러리 전환이 불완전할 수 있습니다.
추가로 다음 사항을 확인해주세요:
🏁 Script executed: #!/bin/bash
# Description: Verify dotenv-java initialization and check for remaining spring-dotenv references
# 1. Search for dotenv-java initialization code
echo "=== Searching for dotenv-java initialization ==="
rg -nC3 'DotEnv\s*\.' --type=java
# 2. Check for any remaining spring-dotenv references
echo "=== Checking for remaining spring-dotenv references ==="
rg -nC3 'spring-dotenv|paulschwarz' --type=java --type=gradle
# 3. Search for @EnableDotEnv or similar Spring-specific annotations
echo "=== Checking for Spring-specific dotenv configurations ==="
rg -nC3 '@EnableDotEnv|spring\.dotenv' --type=javaLength of output: 576 초기 검증 결과, 더 깊은 분석이 필요합니다. 추가 정보를 수집하겠습니다. 추가 검증을 위해 설정 파일과 Application 클래스를 확인하겠습니다. Length of output: 9971 dotenv-java 마이그레이션이 불완전합니다. 즉시 수정이 필요합니다. 검증 결과 다음 문제들을 발견했습니다:
필수 조치:
🤖 Prompt for AI Agents |
||
|
|
||
| // JWT | ||
| implementation("io.jsonwebtoken:jjwt-api:0.12.6") | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| services: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Redis는 AWS ElastiCache를 사용하므로 로컬 컨테이너 제거 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Redis 컨테이너 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redis: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image: redis:alpine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| container_name: redis | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| restart: unless-stopped | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| networks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - wisecard-network | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redis 서비스 구성에 보안 및 데이터 지속성 문제가 있습니다. Redis 서비스를 추가한 것은 좋지만 다음 문제들을 해결해야 합니다:
다음과 같이 개선하는 것을 권장합니다: redis:
image: redis:alpine
container_name: redis
restart: unless-stopped
+ command: redis-server --requirepass ${REDIS_PASSWORD}
+ volumes:
+ - redis_data:/data
+ deploy:
+ resources:
+ limits:
+ memory: 512M
+ cpus: '0.5'
networks:
- wisecard-network그리고 volumes:
app_logs:
driver: local
+ redis_data:
+ driver: local또한, 📝 Committable suggestion
Suggested change
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # WiseCard Backend 애플리케이션 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -24,39 +29,23 @@ services: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_SECRET: ${JWT_SECRET:-} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ports: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "${APP_PORT:-8080}:8080" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "${GRPC_PORT:-9091}:9091" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "${GRPC_PORT:-9090}:9090" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # volumes: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - app_logs:/app/logs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| networks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - wisecard-network | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Redis는 AWS ElastiCache를 사용하므로 의존성 제거 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| healthcheck: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test: ["CMD", "curl", "-f", "http://localhost:8080/api/health/check"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interval: 30s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout: 10s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retries: 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start_period: 40s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Nginx 리버스 프록시 (선택사항) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # nginx: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # image: nginx:alpine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # container_name: wisecard-nginx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # restart: unless-stopped | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ports: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - "80:80" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - "443:443" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # volumes: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - ./nginx/ssl:/etc/nginx/ssl:ro | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # networks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - wisecard-network | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # depends_on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| volumes: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app_logs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| driver: local | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| networks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wisecard-network: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| driver: bridge | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| driver: bridge | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package com.sub.grpc; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 패키지 경로 불일치로 Buf 빌드 실패 이 파일이 🧰 Tools🪛 Buf (1.58.0)3-3: Files with package "com.sub.grpc" must be within a directory "com/sub/grpc" relative to root but were in directory "gRPC/src/main/proto". (PACKAGE_DIRECTORY_MATCH) 🤖 Prompt for AI Agents |
||
|
|
||
| enum CardCompany { | ||
| HANA = 0; | ||
| HYUNDAI = 1; | ||
| KOOKMIN = 2; | ||
| LOTTE = 3; | ||
| SAMSUNG = 4; | ||
| SHINHAN = 5; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,64 +2,72 @@ syntax = "proto3"; | |
|
|
||
| package com.sub.grpc; | ||
|
|
||
| // 온라인/오프라인/BOTH 구분 | ||
| import "google/protobuf/empty.proto"; | ||
| import "cardCompany.proto"; | ||
|
|
||
|
Comment on lines
+5
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 존재하지 않는 proto 파일을 임포트하고 있습니다. 정적 분석에서 🧰 Tools🪛 Buf (1.58.0)6-6: import "cardCompany.proto": file does not exist (COMPILE) 🤖 Prompt for AI Agents |
||
| enum ChannelType { | ||
| ONLINE = 0; | ||
| OFFLINE = 1; | ||
| BOTH = 2; | ||
| } | ||
|
|
||
| // 할인 혜택 | ||
| enum CardType { | ||
| CREDIT = 0; | ||
| DEBIT = 1; | ||
| } | ||
|
|
||
| message DiscountBenefit { | ||
| int64 id = 6; // 변경 식별용 id 추가 | ||
| double rate = 1; // 정률 할인 (0이면 null) | ||
| double amount = 2; // 정액 할인 (0이면 null) | ||
| int64 minimum_amount = 3; // 최소 결제 금액 (0이면 null) | ||
| int64 benefit_limit = 4; // 최대 혜택 한도 (0이면 null) | ||
| ChannelType channel = 5; // 온라인/오프라인/BOTH | ||
| double rate = 1; // 정률 할인 | ||
| int32 amount = 2; // 정액 할인 | ||
| int32 minimum_amount = 3; // 최소 결제 금액 | ||
| int32 benefit_limit = 4; // 최대 혜택 한도 | ||
| int32 minimum_spending = 5; // 전월 최소 실적 (원 단위) | ||
| ChannelType channel = 6; // 온라인/오프라인/BOTH | ||
| } | ||
|
|
||
| // 포인트 적립 혜택 | ||
| message PointBenefit { | ||
| int64 id = 6; // 변경 식별용 id 추가 | ||
| double rate = 1; // 적립률 (0이면 null) | ||
| int64 minimum_amount = 2; // 최소 결제 금액 (0이면 null) | ||
| int64 benefit_limit = 3; // 최대 혜택 한도 (0이면 null) | ||
| ChannelType channel = 4; // 온라인/오프라인/BOTH | ||
| string name = 1; //포인트 이름 | ||
| int32 amount = 2; //적립금 | ||
| double rate = 3; // 적립률 | ||
| int32 minimum_amount = 4; // 최소 결제 금액 | ||
| int32 benefit_limit = 5; // 최대 혜택 한도 | ||
| int32 minimum_spending = 6; // 전월 최소 실적 (원 단위) | ||
| ChannelType channel = 7; // 온라인/오프라인/BOTH | ||
| } | ||
|
|
||
| // 캐시백 혜택 | ||
| message CashbackBenefit { | ||
| int64 id = 6; // 변경 식별용 id 추가 | ||
| double rate = 1; // 캐시백률 (0이면 null) | ||
| double amount = 2; // 정액 캐시백 (0이면 null) | ||
| int64 minimum_amount = 3; // 최소 결제 금액 (0이면 null) | ||
| int64 benefit_limit = 4; // 최대 혜택 한도 (0이면 null) | ||
| ChannelType channel = 5; // 온라인/오프라인/BOTH | ||
| double rate = 1; // 캐시백률 | ||
| int32 amount = 2; // 정액 캐시백 | ||
| int32 minimum_amount = 3; // 최소 결제 금액 | ||
| int32 benefit_limit = 4; // 최대 혜택 한도 | ||
| int32 minimum_spending = 5; // 전월 최소 실적 (원 단위) | ||
| ChannelType channel = 6; // 온라인/오프라인/BOTH | ||
| } | ||
|
|
||
| // 카드 단위 | ||
| message Benefit { | ||
| int64 benefit_id = 1; // 혜택 고유 ID (기존 혜택 식별용) -> 추가한 것 | ||
| repeated DiscountBenefit discounts = 2; // 할인 혜택 배열 | ||
| repeated PointBenefit points = 3; // 포인트 혜택 배열 | ||
| repeated CashbackBenefit cashbacks = 4; // 캐시백 혜택 배열 | ||
| repeated string applicable_category = 5; // 카카오맵 업종 코드 (MT1, FD6, CE7 ...) | ||
| repeated string applicable_targets = 6; // 브랜드명 또는 한글 설명 | ||
| repeated DiscountBenefit discounts = 1; // 할인 혜택 배열 | ||
| repeated PointBenefit points = 2; // 포인트 혜택 배열 | ||
| repeated CashbackBenefit cashbacks = 3; // 캐시백 혜택 배열 | ||
| repeated string categories = 4; // 카카오맵 업종 코드 (MT1, FD6, CE7 ...) | ||
| repeated string targets = 5; // 브랜드명 또는 한글 설명 | ||
| string summary = 6; //한줄 요약 | ||
| } | ||
|
|
||
| message CardBenefit { | ||
| int32 card_id = 1; // 카드 id | ||
| CardCompany card_company = 2; // 카드사 | ||
| string card_name = 3; //카드명 | ||
| string img_url = 4; //카드 이미지 | ||
| CardType card_type = 5; //CREDIT|DEBIT | ||
| repeated Benefit benefits = 6; // 카드에 속한 여러 혜택 | ||
| } | ||
|
|
||
| message CrawledBenefit { | ||
| int64 card_id = 6; // 변경 식별용 id 추가 | ||
| string card_bank = 1; // 카드사 | ||
| string card_name = 2; //카드명 | ||
| string img_url = 3; //카드 이미지 | ||
| string type = 4; //CreditCard|DebitCard | ||
| repeated Benefit benefits = 5; // 카드에 속한 여러 혜택 | ||
| message CardBenefitList { | ||
| repeated CardBenefit cardBenefits = 1; | ||
| } | ||
|
|
||
| // 카드 여러 장 처리 | ||
| message CrawledBenefitList { | ||
| repeated CrawledBenefit crawledBenefit = 1; | ||
| service CardService { | ||
| rpc receiveCardBenefits(CardBenefitList) returns (google.protobuf.Empty); | ||
| } | ||
|
|
||
| // 응답 메시지 | ||
|
|
@@ -69,7 +77,7 @@ message CardSaveResponse { | |
| int32 saved_count = 3; | ||
| } | ||
|
|
||
| // gRPC 서비스 정의 | ||
| // API 서버 저장 서비스 | ||
| service CardDataService { | ||
| rpc SaveCardData(CrawledBenefitList) returns (CardSaveResponse); | ||
| rpc SaveCardData(CardBenefitList) returns (CardSaveResponse); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package com.sub.grpc; | ||
|
|
||
|
|
||
| import "google/protobuf/empty.proto"; | ||
| import "google/protobuf/timestamp.proto"; | ||
| import "cardCompany.proto"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Buf가 🧰 Tools🪛 Buf (1.58.0)8-8: import "cardCompany.proto": file does not exist (COMPILE) 🤖 Prompt for AI Agents |
||
|
|
||
| message CardPromotion { | ||
| CardCompany card_company = 1; // 카드사 | ||
| string description = 2; // 프로모션 설명 | ||
| string img_url = 3; // 프로모션 이미지 | ||
| string url = 4; // 프로모션 링크 | ||
| google.protobuf.Timestamp start_date = 5; // 시작 날짜 | ||
| google.protobuf.Timestamp end_date = 6; // 끝 날짜 | ||
| } | ||
|
|
||
| message CardPromotionList { | ||
| repeated CardPromotion cardPromotion = 1; | ||
| } | ||
|
|
||
| service PromotionService { | ||
| rpc receivePromotions(CardPromotionList) returns (google.protobuf.Empty); | ||
| } | ||
|
|
||
| // 응답 메시지 | ||
| message PromotionSaveResponse { | ||
| bool success = 1; | ||
| string message = 2; | ||
| int32 saved_count = 3; | ||
| } | ||
|
|
||
| // API 서버 저장 서비스 | ||
| service CardPromotionService{ | ||
| rpc SavedPromotions(CardPromotionList) returns (PromotionSaveResponse); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| grpc: | ||
| client: | ||
| local-grpc-server: # ????? ?? | ||
| address: "static://localhost:9090" # ?? ??(??? ??? ?? ??IP) | ||
| local-grpc-server: | ||
| address: "static://localhost:9090" | ||
| negotiationType: "plaintext" | ||
|
|
||
| server: | ||
| port: 50051 | ||
| port: 9092 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||||||||||||
| import com.example.demo.auth.entity.Member; | ||||||||||||||||||||||
| import com.example.demo.auth.jwt.JwtTokenProvider; | ||||||||||||||||||||||
| import com.example.demo.auth.repository.MemberRepository; | ||||||||||||||||||||||
| import com.example.demo.auth.repository.RefreshTokenRepository; | ||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||
|
|
@@ -23,35 +24,19 @@ public class KakaoLoginService { | |||||||||||||||||||||
| private final KakaoOAuthClient kakaoOAuthClient; | ||||||||||||||||||||||
| private final JwtTokenProvider jwtTokenProvider; | ||||||||||||||||||||||
| private final RefreshTokenService refreshTokenService; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||
| public TokenResponse signup(AccessTokenRequest request) { | ||||||||||||||||||||||
| KakaoUserInfo kakaoUserInfo = kakaoOAuthClient.retrieveUserInfo(request.accessToken()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (memberRepository.findBySocialId(kakaoUserInfo.getId()).isPresent()) { | ||||||||||||||||||||||
| throw new RuntimeException("이미 가입된 사용자입니다."); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Member member = Member.builder() | ||||||||||||||||||||||
| .socialId(kakaoUserInfo.getId()) | ||||||||||||||||||||||
| .name(kakaoUserInfo.getNickName()) | ||||||||||||||||||||||
| .email(kakaoUserInfo.getEmail()) | ||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Member newMember = memberRepository.save(member); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| String accessToken = jwtTokenProvider.createAccessToken(newMember.getId()); | ||||||||||||||||||||||
| String refreshToken = jwtTokenProvider.createRefreshToken(newMember.getId()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| refreshTokenService.save(newMember.getId(), refreshToken); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return new TokenResponse(accessToken, refreshToken); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| private final RefreshTokenRepository refreshTokenRepository; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||
| public TokenResponse login(AccessTokenRequest request) { | ||||||||||||||||||||||
| KakaoUserInfo kakaoUserInfo = kakaoOAuthClient.retrieveUserInfo(request.accessToken()); | ||||||||||||||||||||||
| Member member = memberRepository.findBySocialId(kakaoUserInfo.getId()).orElseThrow(); | ||||||||||||||||||||||
| Member member = memberRepository.findBySocialId(kakaoUserInfo.getId()).orElseGet(() -> { | ||||||||||||||||||||||
| Member newMember = Member.builder() | ||||||||||||||||||||||
| .socialId(kakaoUserInfo.getId()) | ||||||||||||||||||||||
| .name(kakaoUserInfo.getNickName()) | ||||||||||||||||||||||
| .email(kakaoUserInfo.getEmail()) | ||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||
|
Comment on lines
+33
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 카카오 사용자 정보 null 검증 필요
Member 생성 전 필수 필드 검증을 추가하세요: Member newMember = Member.builder()
.socialId(kakaoUserInfo.getId())
- .name(kakaoUserInfo.getNickName())
- .email(kakaoUserInfo.getEmail())
+ .name(Optional.ofNullable(kakaoUserInfo.getNickName()).orElse("Unknown"))
+ .email(Optional.ofNullable(kakaoUserInfo.getEmail()).orElse(""))
.build();또는 필수 정보가 없을 경우 명시적으로 예외를 발생시키세요: +if (kakaoUserInfo.getNickName() == null) {
+ throw new IllegalArgumentException("카카오 닉네임 정보가 필요합니다");
+}
Member newMember = Member.builder()
.socialId(kakaoUserInfo.getId())
.name(kakaoUserInfo.getNickName())
.email(kakaoUserInfo.getEmail())
.build();📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||
| return memberRepository.save(newMember); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
Comment on lines
+32
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동시 로그인 시 레이스 컨디션 위험 동일한
다음 중 하나의 해결책을 적용하세요: 해결책 1: 데이터베이스 제약조건 + 예외 처리
@Transactional
public TokenResponse login(AccessTokenRequest request) {
KakaoUserInfo kakaoUserInfo = kakaoOAuthClient.retrieveUserInfo(request.accessToken());
- Member member = memberRepository.findBySocialId(kakaoUserInfo.getId()).orElseGet(() -> {
- Member newMember = Member.builder()
- .socialId(kakaoUserInfo.getId())
- .name(kakaoUserInfo.getNickName())
- .email(kakaoUserInfo.getEmail())
- .build();
- return memberRepository.save(newMember);
- });
+ Member member = findOrCreateMember(kakaoUserInfo);
String accessToken = jwtTokenProvider.createAccessToken(member.getId());
String refreshToken = jwtTokenProvider.createRefreshToken(member.getId());
refreshTokenService.saveOrUpdateToken(member.getId(), refreshToken);
return new TokenResponse(accessToken, refreshToken);
}
+
+private Member findOrCreateMember(KakaoUserInfo kakaoUserInfo) {
+ return memberRepository.findBySocialId(kakaoUserInfo.getId())
+ .orElseGet(() -> {
+ try {
+ Member newMember = Member.builder()
+ .socialId(kakaoUserInfo.getId())
+ .name(kakaoUserInfo.getNickName())
+ .email(kakaoUserInfo.getEmail())
+ .build();
+ return memberRepository.save(newMember);
+ } catch (DataIntegrityViolationException e) {
+ // 동시 생성 시도로 인한 제약조건 위반 - 재조회
+ return memberRepository.findBySocialId(kakaoUserInfo.getId())
+ .orElseThrow(() -> new IllegalStateException("Member creation failed"));
+ }
+ });
+}해결책 2: 비관적 락 사용 별도의 락 메커니즘을 통해 동시 생성 방지: @Transactional
public TokenResponse login(AccessTokenRequest request) {
KakaoUserInfo kakaoUserInfo = kakaoOAuthClient.retrieveUserInfo(request.accessToken());
synchronized (("MEMBER_LOCK_" + kakaoUserInfo.getId()).intern()) {
Member member = memberRepository.findBySocialId(kakaoUserInfo.getId())
.orElseGet(() -> {
Member newMember = Member.builder()
.socialId(kakaoUserInfo.getId())
.name(kakaoUserInfo.getNickName())
.email(kakaoUserInfo.getEmail())
.build();
return memberRepository.save(newMember);
});
String accessToken = jwtTokenProvider.createAccessToken(member.getId());
String refreshToken = jwtTokenProvider.createRefreshToken(member.getId());
refreshTokenService.saveOrUpdateToken(member.getId(), refreshToken);
return new TokenResponse(accessToken, refreshToken);
}
}🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| String accessToken = jwtTokenProvider.createAccessToken(member.getId()); | ||||||||||||||||||||||
| String refreshToken = jwtTokenProvider.createRefreshToken(member.getId()); | ||||||||||||||||||||||
|
|
@@ -66,10 +51,10 @@ public TokenResponse reissue(RefreshTokenRequest request) { | |||||||||||||||||||||
| return jwtTokenProvider.reissueToken(request.refreshToken()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||
| public void withdraw() { | ||||||||||||||||||||||
| Long memberId = getMemberId(); | ||||||||||||||||||||||
| refreshTokenRepository.deleteByMemberId(memberId); | ||||||||||||||||||||||
| memberRepository.deleteById(memberId); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
프로덕션 환경에서 Redis 비밀번호가 비어있습니다.
배포 워크플로우에서 Redis 비밀번호를 빈 값으로 설정하는 것은 보안 위험입니다.
REDIS_PASSWORD를 추가하세요.다음과 같이 수정하세요:
DB_HOST=${{ secrets.DB_HOST }} DB_NAME=db-wisecard DB_USERNAME=admin DB_PASSWORD=${{ secrets.DB_PASSWORD }} DB_PORT=3306 REDIS_HOST=redis REDIS_PORT=6379 - REDIS_PASSWORD= + REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }} APP_API_KAKAO_KEY=${{ secrets.APP_API_KAKAO_KEY }} APP_PORT=8080 GRPC_PORT=9091 JWT_SECRET=${{ secrets.JWT_SECRET }}그리고 GitHub repository settings에서
REDIS_PASSWORD시크릿을 추가해야 합니다.📝 Committable suggestion
🤖 Prompt for AI Agents