docs: Gmail OAuth 404 에러 해결 및 return_url 구현 문서화
- email_sequences.md: Gmail Passport 방식 시퀀스 다이어그램 추가 - authentication_system.md: Gmail Passport 엔드포인트 문서화 - 트러블슈팅: 250901 Gmail OAuth 404 에러 및 return_url 구현 문서 추가
This commit is contained in:
parent
aa2c15f6d8
commit
1c8ef7fa30
@ -112,6 +112,25 @@ sequenceDiagram
|
|||||||
- redirect_uri: "http://localhost:3000"
|
- redirect_uri: "http://localhost:3000"
|
||||||
- callback_url: "https://auth.ro-being.com/..."
|
- callback_url: "https://auth.ro-being.com/..."
|
||||||
|
|
||||||
|
## Gmail Passport 시스템
|
||||||
|
|
||||||
|
### 엔드포인트
|
||||||
|
1. **발급 시작**: `GET /auth/gmail/passport`
|
||||||
|
- Parameters:
|
||||||
|
- `user_id`: 사용자 ID (필수)
|
||||||
|
- `return_url`: OAuth 완료 후 돌아갈 URL (선택)
|
||||||
|
- Response: Google OAuth 페이지로 302 Redirect
|
||||||
|
|
||||||
|
2. **콜백 처리**: `GET /auth/gmail/passport/callback`
|
||||||
|
- Google OAuth 콜백 자동 처리
|
||||||
|
- 토큰 저장 후 return_url 또는 /game으로 리다이렉트
|
||||||
|
- 성공 시: `?gmail=success&email={email}` 파라미터 추가
|
||||||
|
|
||||||
|
### 특징
|
||||||
|
- **원페이지 복귀**: OAuth 인증 후 원래 있던 페이지로 자동 복귀
|
||||||
|
- **상태 관리**: state 파라미터에 return_url 포함하여 전달
|
||||||
|
- **토큰 저장**: gmail_tokens 테이블에 암호화 저장
|
||||||
|
|
||||||
## 보안 고려사항
|
## 보안 고려사항
|
||||||
|
|
||||||
### 구현된 보안 기능
|
### 구현된 보안 기능
|
||||||
|
|||||||
@ -18,46 +18,50 @@
|
|||||||
|
|
||||||
## 1. Gmail OAuth 인증
|
## 1. Gmail OAuth 인증
|
||||||
|
|
||||||
### 1.1 최초 Gmail 연결 (프론트엔드 시작)
|
### 1.1 최초 Gmail 연결 (프론트엔드 시작) - Gmail Passport 방식
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant User as 사용자
|
participant User as 사용자
|
||||||
participant Front as 프론트엔드
|
participant Front as 프론트엔드
|
||||||
participant Gateway as Gateway(8100)
|
|
||||||
participant Auth as auth-server(9000)
|
participant Auth as auth-server(9000)
|
||||||
participant Google as Google OAuth
|
participant Google as Google OAuth
|
||||||
participant DB as PostgreSQL
|
participant DB as PostgreSQL
|
||||||
|
|
||||||
User->>Front: Gmail 연결 버튼 클릭
|
User->>Front: Gmail 연결 버튼 클릭
|
||||||
Front->>Gateway: POST /api/auth/gmail/connect
|
Note over Front: 현재 페이지 URL 저장
|
||||||
Gateway->>Auth: 인증 요청 전달
|
Front->>Auth: GET /auth/gmail/passport
|
||||||
|
Note over Auth: ?user_id={userId}<br/>&return_url={currentUrl}
|
||||||
|
|
||||||
Auth->>Auth: state 토큰 생성
|
Auth->>Auth: state 데이터 생성
|
||||||
Auth->>DB: state 임시 저장
|
Note over Auth: {user_id, timestamp,<br/>return_url} → JSON
|
||||||
Auth->>Auth: OAuth URL 생성
|
Auth->>Auth: OAuth URL 생성
|
||||||
Note over Auth: client_id, redirect_uri,<br/>scopes, state 포함
|
Note over Auth: client_id, redirect_uri,<br/>scopes, state 포함
|
||||||
|
|
||||||
Auth-->>Gateway: OAuth URL 반환
|
Auth-->>Front: 302 Redirect to Google
|
||||||
Gateway-->>Front: 리다이렉트 URL
|
|
||||||
Front->>Google: 브라우저 리다이렉트
|
Front->>Google: 브라우저 리다이렉트
|
||||||
|
|
||||||
User->>Google: Google 계정 로그인
|
User->>Google: Google 계정 로그인
|
||||||
User->>Google: 권한 승인
|
User->>Google: 권한 승인
|
||||||
Note over Google: gmail.send<br/>gmail.readonly<br/>gmail.modify
|
Note over Google: gmail.send<br/>gmail.readonly<br/>gmail.modify
|
||||||
|
|
||||||
Google->>Auth: 인증 코드 콜백
|
Google->>Auth: GET /auth/gmail/passport/callback
|
||||||
Note over Auth: /api/auth/gmail/callback
|
Note over Auth: ?code={auth_code}&state={state}
|
||||||
|
|
||||||
Auth->>DB: state 검증
|
Auth->>Auth: state 파싱 및 검증
|
||||||
|
Note over Auth: user_id, return_url 추출
|
||||||
Auth->>Google: 토큰 교환 요청
|
Auth->>Google: 토큰 교환 요청
|
||||||
Google-->>Auth: access_token, refresh_token
|
Google-->>Auth: access_token, refresh_token
|
||||||
|
|
||||||
Auth->>DB: gmail_tokens 테이블 저장
|
Auth->>DB: gmail_tokens 테이블 저장
|
||||||
Note over DB: user_id, email,<br/>tokens (암호화),<br/>scopes, metadata
|
Note over DB: user_id, email,<br/>tokens (암호화),<br/>scopes, metadata
|
||||||
|
|
||||||
Auth-->>Front: 인증 완료 리다이렉트
|
Auth->>Auth: 리다이렉트 URL 결정
|
||||||
Front->>Front: 성공 메시지 표시
|
Note over Auth: return_url 있으면 해당 페이지<br/>없으면 /game 기본값
|
||||||
|
|
||||||
|
Auth-->>Front: 302 Redirect
|
||||||
|
Note over Front: {return_url}?gmail=success<br/>&email={email}
|
||||||
|
Front->>Front: 원래 페이지에서 성공 메시지 표시
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.2 Slack에서 Gmail 연결
|
### 1.2 Slack에서 Gmail 연결
|
||||||
|
|||||||
@ -0,0 +1,144 @@
|
|||||||
|
# Gmail OAuth 404 에러 및 Return URL 구현
|
||||||
|
|
||||||
|
## 작성일: 2025-09-01
|
||||||
|
## 작성자: happybell80
|
||||||
|
## 관련 서비스: auth-server, frontend-customer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18시 30분 - 문제 발견
|
||||||
|
|
||||||
|
### 문제 상황
|
||||||
|
1. **Frontend 404 오류**
|
||||||
|
- Gmail OAuth 성공 후 `/settings/items` 페이지로 리다이렉트
|
||||||
|
- 해당 라우트가 Frontend에 존재하지 않아 404 에러 발생
|
||||||
|
- 실제 아이템 관리 페이지는 `/inventory`
|
||||||
|
|
||||||
|
2. **UX 문제**
|
||||||
|
- OAuth 인증 후 `/inventory` 페이지로 이동
|
||||||
|
- 사용자가 원래 있던 페이지로 돌아가려면 수동 이동 필요
|
||||||
|
|
||||||
|
### 원인 분석
|
||||||
|
```typescript
|
||||||
|
// frontend-customer/src/App.tsx
|
||||||
|
<Route path="/inventory" component={InventoryPage} /> // 존재
|
||||||
|
<Route path="/settings/items" ... /> // 존재하지 않음
|
||||||
|
|
||||||
|
// auth-server/app/providers/gmail_passport.py:223
|
||||||
|
return RedirectResponse(f"https://ro-being.com/settings/items?gmail=success&email={email}")
|
||||||
|
// 잘못된 경로로 리다이렉트
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18시 45분 - 1차 수정: 404 에러 해결
|
||||||
|
|
||||||
|
### 수정 내용
|
||||||
|
```python
|
||||||
|
# auth-server/app/providers/gmail_passport.py:223
|
||||||
|
# 변경 전
|
||||||
|
return RedirectResponse(f"https://ro-being.com/settings/items?gmail=success&email={email}")
|
||||||
|
|
||||||
|
# 변경 후
|
||||||
|
return RedirectResponse(f"https://ro-being.com/inventory?gmail=success&email={email}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 결과
|
||||||
|
- 404 에러 해결 ✅
|
||||||
|
- `/inventory` 페이지로 정상 리다이렉트
|
||||||
|
- 하지만 여전히 원래 페이지로 돌아가지 못함
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19시 00분 - 2차 수정: Return URL 구현
|
||||||
|
|
||||||
|
### 구현 전략
|
||||||
|
1. OAuth 요청 시 현재 페이지 URL을 `return_url` 파라미터로 전달
|
||||||
|
2. auth-server에서 state에 return_url 저장
|
||||||
|
3. OAuth 콜백 처리 시 return_url로 리다이렉트
|
||||||
|
4. return_url이 없으면 기본값 `/game` 사용
|
||||||
|
|
||||||
|
### 수정 내용
|
||||||
|
|
||||||
|
#### 1. Frontend - OAuth 요청에 return_url 추가
|
||||||
|
```typescript
|
||||||
|
// frontend-customer/src/components/skills-items-panel.tsx:359-364
|
||||||
|
// 변경 전
|
||||||
|
const authUrl = `https://auth.ro-being.com/auth/gmail/passport?user_id=${userId}`;
|
||||||
|
sessionStorage.setItem('gmail_oauth_return_url', window.location.href);
|
||||||
|
window.location.href = authUrl;
|
||||||
|
|
||||||
|
// 변경 후
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
const authUrl = `https://auth.ro-being.com/auth/gmail/passport?user_id=${userId}&return_url=${encodeURIComponent(currentUrl)}`;
|
||||||
|
window.location.href = authUrl;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. auth-server - return_url 처리 추가
|
||||||
|
```python
|
||||||
|
# app/providers/gmail_passport.py
|
||||||
|
|
||||||
|
# 1) 발급 엔드포인트 수정 (line 58)
|
||||||
|
@router.get("/")
|
||||||
|
async def issue_passport(user_id: str = Query(...), return_url: str = Query(default=None)):
|
||||||
|
# state에 return_url 포함
|
||||||
|
state_data = {"user_id": user_id, "timestamp": time.time()}
|
||||||
|
if return_url:
|
||||||
|
state_data["return_url"] = return_url
|
||||||
|
state = json.dumps(state_data)
|
||||||
|
|
||||||
|
# 2) 콜백 처리 수정 (line 225-235)
|
||||||
|
# return_url이 있으면 해당 페이지로, 없으면 기본 페이지로
|
||||||
|
return_url = state_data.get("return_url")
|
||||||
|
if return_url:
|
||||||
|
separator = "&" if "?" in return_url else "?"
|
||||||
|
redirect_url = f"{return_url}{separator}gmail=success&email={email}"
|
||||||
|
else:
|
||||||
|
redirect_url = f"https://ro-being.com/game?gmail=success&email={email}"
|
||||||
|
|
||||||
|
return RedirectResponse(redirect_url)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19시 15분 - 배포 및 검증
|
||||||
|
|
||||||
|
### Git 커밋
|
||||||
|
```bash
|
||||||
|
# auth-server
|
||||||
|
git commit -m "feat: OAuth 인증 후 이전 페이지로 자동 복귀 구현"
|
||||||
|
|
||||||
|
# frontend-customer
|
||||||
|
git commit -m "feat: Gmail OAuth 요청 시 return_url 파라미터 추가"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 테스트 시나리오
|
||||||
|
1. `/game` 페이지에서 Gmail 연결 → OAuth 완료 → `/game` 복귀 ✅
|
||||||
|
2. `/game001` 페이지에서 Gmail 연결 → OAuth 완료 → `/game001` 복귀 ✅
|
||||||
|
3. return_url 없이 직접 접근 → OAuth 완료 → `/game` (기본값) ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 교훈
|
||||||
|
|
||||||
|
### 개발 원칙
|
||||||
|
1. **라우트 변경 시 양쪽 확인**: Frontend 라우트 변경 시 Backend 리다이렉트 경로도 함께 확인
|
||||||
|
2. **UX 우선**: OAuth 같은 외부 플로우는 원래 페이지로 복귀가 기본
|
||||||
|
3. **파라미터 전달**: state 파라미터를 활용한 컨텍스트 유지
|
||||||
|
|
||||||
|
### 기술적 포인트
|
||||||
|
1. **URL 인코딩**: return_url은 반드시 `encodeURIComponent()` 처리
|
||||||
|
2. **쿼리 파라미터 처리**: 기존 쿼리 있는 URL에 파라미터 추가 시 `?` vs `&` 구분
|
||||||
|
3. **기본값 설정**: return_url 없을 때 합리적인 기본값 제공
|
||||||
|
|
||||||
|
### 문서 업데이트
|
||||||
|
- `300_architecture/sequences/email_sequences.md`: Gmail Passport 플로우 추가
|
||||||
|
- `300_architecture/380_authentication_system.md`: Gmail Passport 엔드포인트 문서화
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 참고 자료
|
||||||
|
- 관련 PR: auth-server #fc94303, frontend-customer #26e7f70
|
||||||
|
- 영향받는 파일:
|
||||||
|
- auth-server/app/providers/gmail_passport.py
|
||||||
|
- frontend-customer/src/components/skills-items-panel.tsx
|
||||||
Loading…
x
Reference in New Issue
Block a user