diff --git a/backend/app.py b/backend/app.py index c2191c0..b349cb3 100644 --- a/backend/app.py +++ b/backend/app.py @@ -649,7 +649,15 @@ def claim(): except Exception as e: logging.warning(f"품목 조회 실패 (transaction_id={transaction_id}): {e}") - return render_template('claim_form.html', token_info=token_info, sale_items=sale_items) + # JS SDK용 카카오 state 생성 (CSRF 보호) + csrf_token = secrets.token_hex(16) + state_data = {'t': token_param, 'csrf': csrf_token} + kakao_state = base64.urlsafe_b64encode( + json.dumps(state_data).encode() + ).decode() + session['kakao_csrf'] = csrf_token + + return render_template('claim_form.html', token_info=token_info, sale_items=sale_items, kakao_state=kakao_state) @app.route('/api/claim', methods=['POST']) @@ -876,6 +884,11 @@ def claim_kakao_callback(): kakao_phone_raw = user_info.get('phone_number') kakao_phone = normalize_kakao_phone(kakao_phone_raw) + # 카카오에서 받은 생년월일 조합 (YYYY-MMDD) + kakao_birthday = None + if user_info.get('birthyear') and user_info.get('birthday'): + kakao_birthday = f"{user_info['birthyear']}-{user_info['birthday'][:2]}-{user_info['birthday'][2:]}" + # 7. 분기: 전화번호가 있으면 자동 적립, 없으면 폰 입력 폼 if kakao_phone: # 자동 적립 @@ -883,8 +896,13 @@ def claim_kakao_callback(): if existing_user_id: user_id = existing_user_id is_new = False + # 생년월일이 있으면 업데이트 + if kakao_birthday: + conn = db_manager.get_sqlite_connection() + conn.cursor().execute("UPDATE users SET birthday = ? WHERE id = ? AND birthday IS NULL", (kakao_birthday, user_id)) + conn.commit() else: - user_id, is_new = get_or_create_user(kakao_phone, kakao_name) + user_id, is_new = get_or_create_user(kakao_phone, kakao_name, birthday=kakao_birthday) link_kakao_identity(user_id, kakao_id, user_info) @@ -1006,7 +1024,14 @@ def my_page(): phone = request.args.get('phone', '') if not phone: - return render_template('my_page_login.html') + # JS SDK용 카카오 state 생성 + csrf_token = secrets.token_hex(16) + state_data = {'purpose': 'mypage', 'csrf': csrf_token} + kakao_state = base64.urlsafe_b64encode( + json.dumps(state_data).encode() + ).decode() + session['kakao_csrf'] = csrf_token + return render_template('my_page_login.html', kakao_state=kakao_state) # 전화번호로 사용자 조회 phone = phone.replace('-', '').replace(' ', '') diff --git a/backend/services/kakao_client.py b/backend/services/kakao_client.py index 17f1009..a6313ad 100644 --- a/backend/services/kakao_client.py +++ b/backend/services/kakao_client.py @@ -39,7 +39,7 @@ class KakaoAPIClient: 'client_id': self.client_id, 'redirect_uri': self.redirect_uri, 'response_type': 'code', - 'scope': 'profile_nickname,profile_image,account_email,name,phone_number,birthday,birthyear' + 'scope': 'profile_nickname,profile_image,account_email,name,phone_number,birthday' } if state: diff --git a/backend/templates/claim_form.html b/backend/templates/claim_form.html index e24786f..a0a96ed 100644 --- a/backend/templates/claim_form.html +++ b/backend/templates/claim_form.html @@ -569,17 +569,17 @@
- 카카오로 적립하기 - +
@@ -732,6 +732,28 @@ successScreen.style.display = 'block'; } + + + + diff --git a/docs/kakao-oauth-setup.md b/docs/kakao-oauth-setup.md index 5bd9a7a..e3b56dc 100644 --- a/docs/kakao-oauth-setup.md +++ b/docs/kakao-oauth-setup.md @@ -7,6 +7,118 @@ - **앱 유형**: 비즈 앱 - **개발자 콘솔**: https://developers.kakao.com/console/app/1165131 +--- + +## 플랫폼 키 (앱 > 플랫폼 키) + +| 키 종류 | 값 | 용도 | +|---------|---|------| +| **Native App Key** | `346b84c4e018e20f0f8` | Android/iOS 네이티브 앱 (현재 미사용) | +| **JavaScript Key** | `3d1e098107157c5021b73bd5ab48600f` | 카카오 JS SDK (프론트엔드) | +| **REST API Key** | `caad27ac4bc92d8dc83bdd6aae744811` | 서버 간 API 호출 (현재 사용 중) | +| **Admin Key** | (콘솔에서 확인) | 서버 관리 기능 (사용 주의) | + +### 키 사용 구분 + +``` +[현재 구현] REST API 방식 + 프론트엔드 → 302 리다이렉트 → 카카오 웹 로그인 페이지 → 콜백 + 사용 키: REST API Key (서버 환경변수 KAKAO_CLIENT_ID) + +[향후 전환] JS SDK 방식 + 프론트엔드 → Kakao.Auth.authorize() → 카카오톡 앱 직접 실행 → 콜백 + 사용 키: JavaScript Key (프론트엔드 HTML에 노출) +``` + +### Client Secret + +``` +앱 > 보안 > Client Secret 코드 +``` +- 환경변수: `KAKAO_CLIENT_SECRET` +- REST API 토큰 교환 시 필수 + +--- + +## REST API vs JS SDK 비교 + +| 항목 | REST API (폴백) | JS SDK (현재 적용) | +|------|----------------|-------------------| +| **인증 키** | REST API Key | JavaScript Key | +| **로그인 UX** | 웹 브라우저에서 카카오 로그인 페이지 표시 (매번 동의 확인) | 카카오톡 앱이 직접 열림 → 원탭 동의 → 즉시 복귀 | +| **모바일 경험** | 웹뷰 로그인 (느림) | 앱 ↔ 앱 전환 (빠름) | +| **앱 미설치 시** | 웹 로그인 표시 | 자동으로 웹 로그인 폴백 | +| **백엔드** | `kakao_client.get_authorization_url()` | 변경 없음 (콜백 동일) | +| **보안** | 키가 서버에만 존재 | JavaScript Key는 공개 가능 (도메인 제한으로 보호) | + +### 현재 적용 상태 (JS SDK) + +JS SDK가 적용된 페이지: +- `claim_form.html` — QR 적립 시 "카카오로 적립하기" 버튼 +- `my_page_login.html` — 마이페이지 "카카오로 조회하기" 버튼 + +서버 리다이렉트 유지 페이지 (보조 진입점): +- `index.html`, `my_page.html`, `signup.html`, `error.html` → `/my-page/kakao/start` + +### JS SDK 동작 방식 + +``` +모바일: + 카카오톡 앱 설치됨 → 앱으로 전환 (원탭 로그인) → 콜백 + 카카오톡 앱 미설치 → 웹 로그인 페이지로 자동 폴백 → 콜백 + +PC: + 항상 웹 로그인 페이지 표시 → 콜백 + +JS SDK 로드 실패 시: + 서버 리다이렉트 폴백 (/claim/kakao/start 또는 /my-page/kakao/start) +``` + +### 다른 카카오 계정으로 적립 (향후 구현) + +폰이 2대이거나 다른 계정으로 적립하고 싶은 경우: + +```javascript +// 기본: 카카오톡 앱 계정으로 바로 로그인 +Kakao.Auth.authorize({ + redirectUri: '...', + state: '...' +}); + +// 다른 계정으로: 기존 세션 무시, 계정 입력 강제 +Kakao.Auth.authorize({ + redirectUri: '...', + state: '...', + prompt: 'login' // ← 핵심 파라미터 +}); +``` + +UI 구성안: +``` +┌──────────────────────────────────┐ +│ [카카오로 적립하기] │ ← 기본 (앱 → 원탭) +│ │ +│ 다른 카카오 계정으로 적립 → │ ← prompt:'login' +└──────────────────────────────────┘ +``` + +### 카카오 개발자 콘솔 필수 설정 + +> **중요**: JS SDK 사용 시 JavaScript 키에도 Redirect URI 등록 필요 + +``` +앱 > 플랫폼 키 > JavaScript 키 클릭 > 리다이렉트 URI +→ https://mile.0bin.in/claim/kakao/callback 추가 +``` + +Web 플랫폼 도메인도 등록 확인: +``` +앱 > 플랫폼 > Web > 사이트 도메인 +→ https://mile.0bin.in 포함 확인 +``` + +--- + ## Redirect URI 등록 (2025년 12월 개편 후) > **주의**: 2025년 12월 카카오 콘솔 UI가 개편되면서 Redirect URI 위치가 변경됨. @@ -18,12 +130,6 @@ 앱 > 플랫폼 키 > REST API 키 클릭 > 리다이렉트 URI ``` -### 이전 경로 (~ 2025.11, 더 이상 사용 안 함) - -``` -카카오 로그인 > 일반 > Redirect URI ← 여기 더 이상 없음 -``` - ### 등록된 Redirect URI 목록 | 서비스 | Redirect URI | @@ -41,6 +147,8 @@ 로그인용 Redirect URI와 혼동하지 않도록 주의. +--- + ## 웹 도메인 등록 ``` @@ -59,51 +167,73 @@ - `https://ka.0bin.in` - `https://mile.0bin.in` +--- + ## 동의항목 설정 ``` 카카오 로그인 > 동의항목 ``` -| 항목 | ID | 용도 | 비즈앱 필요 | -|------|-----|------|------------| -| 닉네임 | profile_nickname | 사용자 이름 | X | -| 프로필 사진 | profile_image | 아바타 | X | -| 이메일 | account_email | 계정 연동 | X | -| 이름 (실명) | name | 마일리지 적립자명 | O | -| 전화번호 | phone_number | 마일리지 유저 매칭 | O | +| 항목 | ID | 동의 목적 | 상태 | 비즈앱 필요 | +|------|-----|----------|------|------------| +| 닉네임 | profile_nickname | 사용자 식별 | 승인 | X | +| 프로필 사진 | profile_image | 아바타 표시 | 승인 | X | +| 이메일 | account_email | 계정 연동 | 승인 | X | +| 이름 (실명) | name | 마일리지 적립자명 | 승인 | O | +| 전화번호 | phone_number | 마일리지 적립 계정 식별 및 포인트 조회 | 승인 | O | +| 생일 | birthday | 생일 기념 포인트 2배 적립 이벤트 제공 | 승인 | O | +| 출생연도 | birthyear | 생일 기념 포인트 2배 적립 이벤트 제공 | 권한 없음 (미승인) | O | + +### 현재 사용 중인 스코프 + +``` +profile_nickname,profile_image,account_email,name,phone_number,birthday +``` + +> ⚠️ `birthyear`는 아직 권한 미승인 상태. 스코프에 포함하면 **KOE205 에러** 발생. +> 승인되면 스코프에 추가하고, `kakao_client.py`의 scope 문자열 수정 필요. + +--- ## 환경변수 ```bash -KAKAO_CLIENT_ID= +# 카카오 OAuth (REST API 방식) +KAKAO_CLIENT_ID=caad27ac4bc92d8dc83bdd6aae744811 # REST API Key KAKAO_CLIENT_SECRET=<카카오 개발자 콘솔 > 앱 > 보안에서 확인> KAKAO_REDIRECT_URI=https://mile.0bin.in/claim/kakao/callback + +# JS SDK 전환 시 추가 (프론트엔드 전용, 서버 환경변수 불필요) +# JavaScript Key: 3d1e098107157c5021b73bd5ab48600f ``` -### Client ID 확인 위치 - -``` -앱 > 플랫폼 키 > REST API 키 > 키 값 -``` - -### Client Secret 확인 위치 - -``` -앱 > 보안 > Client Secret 코드 -``` +--- ## 관련 파일 | 프로젝트 | 파일 | 설명 | |---------|------|------| -| pharmacy-pos-qr-system | `backend/services/kakao_client.py` | 카카오 API 클라이언트 | +| pharmacy-pos-qr-system | `backend/services/kakao_client.py` | 카카오 API 클라이언트 (REST API 방식) | | pharmacy-pos-qr-system | `backend/app.py` | OAuth 라우트 (`/claim/kakao/*`) | | board-system-project | `backend/services/kakao_client.py` | 카카오 API 클라이언트 (원본) | | board-system-project | `backend/routes/auth.py` | OAuth 라우트 (`/auth/kakao/*`) | +--- + +## 카카오 데이터 포맷 참고 + +| 필드 | 포맷 | 예시 | DB 저장 | +|------|------|------|---------| +| birthday | MMDD | `0315` | `YYYY-MM-DD`로 변환 | +| birthyear | YYYY | `1990` | birthday와 결합 | +| phone_number | +82 10-XXXX-XXXX | `+82 10-2130-7390` | 하이픈/국가코드 제거 후 저장 | + +--- + ## 참고 링크 - [카카오 로그인 REST API 문서](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api) +- [카카오 JS SDK 문서](https://developers.kakao.com/docs/latest/ko/javascript/getting-started) - [카카오 로그인 설정하기](https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite) - [카카오 앱 키 구조 개편 공지 (2025.12)](https://devtalk.kakao.com/t/upcoming-kakao-developers-app-key-update/147295)