diff --git a/300_architecture/380_authentication_system.md b/300_architecture/380_authentication_system.md index 3b898cc..6a120d6 100644 --- a/300_architecture/380_authentication_system.md +++ b/300_architecture/380_authentication_system.md @@ -32,6 +32,15 @@ - GET/POST 콜백 모두 지원 - 하이브리드 사용자 매핑 +#### NAVER WORKS OAuth +- **엔드포인트**: `/auth/naverworks/login` +- **콜백**: `/auth/naverworks/callback` +- **특징**: + - OAuth 2.0 + OpenID Connect + - Service Account (JWT) 지원 + - Access Token 1시간, Refresh Token Rotation + - **결정필요**: Redirect URL 도메인 (auth.ro-being.com vs auth.robeing.com) + ### 3. 데이터베이스 구조 #### users 테이블 @@ -154,11 +163,18 @@ SLACK_CLIENT_ID= SLACK_CLIENT_SECRET= SLACK_REDIRECT_URI= -# Gmail OAuth +# Gmail OAuth GMAIL_CLIENT_ID= GMAIL_CLIENT_SECRET= GMAIL_REDIRECT_URI= +# NAVER WORKS OAuth +NAVERWORKS_CLIENT_ID= +NAVERWORKS_CLIENT_SECRET= +NAVERWORKS_REDIRECT_URI= +NAVERWORKS_SERVICE_ACCOUNT= +NAVERWORKS_PRIVATE_KEY_PATH= + # Redis REDIS_URL=redis://localhost:6379/0 diff --git a/ideas/250916_네이버웍스_캘린더_API_연동_가이드.md b/ideas/250916_네이버웍스_캘린더_API_연동_가이드.md index 80a5702..134b099 100644 --- a/ideas/250916_네이버웍스_캘린더_API_연동_가이드.md +++ b/ideas/250916_네이버웍스_캘린더_API_연동_가이드.md @@ -2,6 +2,19 @@ **[현재 프로젝트 상태]** 네이버웍스 API 연동 미구현 (OAuth2 토큰 관리 로직 없음, 관련 skill 서비스 미존재, auth-server에 Works API 인증 미탑재). +## 구현 현황 및 필요 작업 + +### 현재 상태 +- **auth-server**: NAVER WORKS OAuth 미구현 (Gmail, Slack만 존재) +- **skill 서비스**: NAVER WORKS 전용 스킬 없음 +- **nginx**: NAVER WORKS 라우팅 설정 없음 + +### 확인된 NAVER WORKS OAuth 2.0 엔드포인트 +- **Authorization**: `https://auth.worksmobile.com/oauth2/v2.0/authorize` +- **Token**: `https://auth.worksmobile.com/oauth2/v2.0/token` +- **Userinfo (OIDC)**: `https://www.worksapis.com/v1.0/oidc/userinfo` +- **API Base**: `https://www.worksapis.com/v1.0/` + --- ## 1. 로빙(RO-BEING) 앱 설정 현황 @@ -11,10 +24,10 @@ ### 1.1. 인증 정보 -- **Client ID**: `kFJu_ajl6tb8pBiPEEnS` -- **Client Secret**: `xIz5G0v4wn` +- **Client ID**: `[환경변수 NAVERWORKS_CLIENT_ID 참조]` +- **Client Secret**: `[환경변수 NAVERWORKS_CLIENT_SECRET 참조]` - > **⚠️ 경고: 이 값은 외부에 노출되어서는 안 되며, Git 등 버전 관리 시스템에 포함하지 마십시오. Vault 또는 암호화된 환경 변수를 통해 안전하게 관리해야 합니다.** -- **Service Account**: `86rye.serviceaccount@company-x.partners` +- **Service Account**: `[환경변수 NAVERWORKS_SERVICE_ACCOUNT 참조]` ### 1.2. 토큰 설정 @@ -102,11 +115,69 @@ - **JWT 키 관리 (JWKS)**: 게이트웨이는 JWT 서명에 사용하는 공개키 목록을 `/.well-known/jwks.json` 엔드포인트를 통해 노출하여, 다른 서비스들이 토큰을 안전하게 검증하고 키 교체에 대응할 수 있도록 합니다. - **토큰 흐름**: 로빙/스킬 서버는 외부 API를 직접 호출하는 대신, 게이트웨이에 API 호출을 위임하고 필요한 단기 토큰을 받아 사용합니다. -## 5. 참고 자료 +## 5. 구현 필요 사항 + +### 5.1 파일 생성/수정 목록 + +#### 생성 필요 +- **`auth-server/app/providers/naverworks.py`**: OAuth 2.0 인증 플로우 구현 +- **`skill-naverworks/`**: NAVER WORKS API 전용 스킬 서비스 (포트 8511) + +#### 수정 필요 +- **`auth-server/app/main.py`**: naverworks 라우터 등록 (`prefix="/auth/naverworks"`) +- **`auth-server/.env`**: NAVERWORKS_CLIENT_ID, SECRET, REDIRECT_URI 추가 +- **`nginx-deploy`**: `/auth/naverworks`, `/api/naverworks` 라우팅 설정 + +### 5.2 구현 플로우 (Slack 패턴 참조) + +#### OAuth 로그인 플로우 +```python +# auth-server/app/providers/naverworks.py 구현 필요 + +@router.get("/login/") +async def naverworks_login(): + # 1. State 생성 → Redis 저장 (TTL 300s) + # 2. Redirect to https://auth.worksmobile.com/oauth2/v2.0/authorize + +@router.get("/callback") +@router.post("/callback") # form_post 지원 +async def naverworks_login_callback(): + # 1. State 검증 + # 2. Code → Token 교환 (POST https://auth.worksmobile.com/oauth2/v2.0/token) + # 3. Userinfo 조회 (GET https://www.worksapis.com/v1.0/oidc/userinfo) + # 4. User DB 매핑 (oauth_provider="naverworks") + # 5. JWT 생성 → Redis temp code → Frontend redirect +``` + +#### Service Account JWT 인증 +```python +async def get_service_account_token(): + # 1. Private Key 로드 (파일 시스템) + # 2. JWT Assertion 생성 + # 3. Token 요청 (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer) + # 4. Access Token 캐싱 (Redis TTL 3600s) +``` + +### 5.3 결정/확인 필요 사항 + +#### 결정사항 (확정) +- **Redirect URL 도메인**: `auth.ro-being.com` 사용 +- **Private Key 저장 경로**: `auth-server/private_20250917185550.key` +- **NAVER WORKS 토큰 테이블**: `naverworks_token` (team 스키마 아래, 단수형) + +#### 확인필요 +- **OIDC userinfo 응답 형식**: sub, userId 등 정확한 필드명 +- **Service Account JWT 서명**: 알고리즘 (RS256 등) +- **Rate Limit**: API 호출 제한 및 쿼터 +- **Private Key 교체 시**: 기존 토큰 유효성 유지 여부 + +## 6. 참고 자료 - **네이버웍스 개발자 센터 (공식 문서):** [developers.worksmobile.com](https://developers.worksmobile.com) - [인증 가이드](https://developers.worksmobile.com/kr/docs/auth) - [캘린더 API](https://developers.worksmobile.com/kr/document/10070?lang=ko) - [주소록 API](https://developers.worksmobile.com/kr/docs/contact) - [메일 API](https://developers.worksmobile.com/kr/docs/mail) -- **네이버 개발자 포럼:** API 관련 실제 질의응답을 참고할 수 있습니다. +- **내부 참조 코드:** + - Slack OAuth: `auth-server/app/providers/slack.py` + - Gmail OAuth: `auth-server/app/providers/gmail_passport.py` diff --git a/troubleshooting/250917_naverworks_oauth_implementation.md b/troubleshooting/250917_naverworks_oauth_implementation.md new file mode 100644 index 0000000..cda546f --- /dev/null +++ b/troubleshooting/250917_naverworks_oauth_implementation.md @@ -0,0 +1,332 @@ +# NAVER WORKS OAuth 2.0 구현 가이드 + +**작성일**: 2025-09-17 +**작성자**: happybell80 +**상태**: 구현 대기 + +## 1. 현재 상태 분석 + +### 1.1 기존 Slack OAuth 구현 확인 +**파일 위치**: `auth-server/app/providers/slack.py` + +#### Slack OAuth 엔드포인트 구조 +```python +# OIDC 사용자 로그인 +GET /auth/slack/login/ → slack_login() +GET|POST /auth/slack/login/callback → slack_login_callback() + +# Bot 설치 (Passport) +GET /auth/slack/passport/install → slack_install() +GET /auth/slack/passport/callback → slack_callback() +GET /auth/slack/passport/status/{workspace_id} → slack_status() +DELETE /auth/slack/passport/uninstall/{workspace_id} → slack_uninstall() +``` + +#### Slack OAuth URL +- **OIDC Authorize**: `https://slack.com/openid/connect/authorize` +- **OIDC Token**: `https://slack.com/api/openid.connect.token` +- **OIDC Userinfo**: `https://slack.com/api/openid.connect.userInfo` +- **Bot Authorize**: `https://slack.com/oauth/v2/authorize` +- **Bot Token**: `https://slack.com/api/oauth.v2.access` + +#### Slack 구현 패턴 +1. State 생성 → Redis 저장 (TTL 300s) +2. OAuth 리다이렉트 +3. Callback에서 state 검증 +4. Code → Token 교환 +5. Userinfo 조회 +6. DB User 매핑 (oauth_provider="slack", oauth_id=sub) +7. JWT 생성 → Redis temp code 저장 +8. Frontend로 리다이렉트 + +### 1.2 NAVER WORKS 현재 설정 +**문서**: `DOCS/ideas/250916_네이버웍스_캘린더_API_연동_가이드.md` + +- **앱 이름**: Ro-being +- **소속**: company-x.partners (155032) +- **Redirect URL (콘솔 설정)**: `https://auth.robeing.com/oauth/naverworks/callback` +- **활성 Scopes**: openid, profile, email, calendar, contact, file, mail, task, user +- **Token 설정**: Access Token 1시간, Refresh Token Rotation On + +## 2. NAVER WORKS OAuth 2.0 구현 명세 + +### 2.1 공식 OAuth 엔드포인트 +**확인된 LINE WORKS API 2.0 엔드포인트**: +- **Authorization**: `https://auth.worksmobile.com/oauth2/v2.0/authorize` +- **Token**: `https://auth.worksmobile.com/oauth2/v2.0/token` +- **Userinfo**: `https://www.worksapis.com/v1.0/oidc/userinfo` +- **API Base**: `https://www.worksapis.com/v1.0/` + +### 2.2 구현 필요 파일 + +#### 신규 생성 +1. **`auth-server/app/providers/naverworks.py`** +```python +from fastapi import APIRouter, Request, HTTPException, Depends +from fastapi.responses import RedirectResponse +import httpx +import jwt +import json +import uuid +from datetime import datetime, timedelta +from app.core.auth import create_access_token +from app.models.user import User +from app.core.config import settings +from app.db.redis_client import redis_client + +router = APIRouter() + +NAVERWORKS_AUTH_URL = "https://auth.worksmobile.com/oauth2/v2.0/authorize" +NAVERWORKS_TOKEN_URL = "https://auth.worksmobile.com/oauth2/v2.0/token" +NAVERWORKS_USERINFO_URL = "https://www.worksapis.com/v1.0/oidc/userinfo" +NAVERWORKS_API_BASE = "https://www.worksapis.com/v1.0" + +@router.get("/login/") +async def naverworks_login(request: Request, redirect_uri: str = None): + """NAVER WORKS OAuth 로그인 시작""" + # 구현 내용... + +@router.get("/callback") +@router.post("/callback") # form_post 지원 +async def naverworks_login_callback(request: Request): + """NAVER WORKS OAuth 콜백 처리""" + # 구현 내용... + +# Service Account JWT 인증 헬퍼 +async def get_service_account_token(): + """서비스 계정으로 Access Token 발급""" + # JWT assertion 생성 + # Token endpoint 호출 + # 구현 내용... +``` + +#### 기존 파일 수정 +2. **`auth-server/app/main.py`** +```python +# 추가 +from app.providers import naverworks + +# 라우터 등록 +app.include_router( + naverworks.router, + prefix="/auth/naverworks", + tags=["auth-naverworks"] +) +``` + +3. **`auth-server/.env`** +```bash +# NAVER WORKS OAuth +NAVERWORKS_CLIENT_ID=[Client ID from console] +NAVERWORKS_CLIENT_SECRET=[Client Secret from console] +NAVERWORKS_REDIRECT_URI=https://auth.ro-being.com/auth/naverworks/callback +NAVERWORKS_SERVICE_ACCOUNT=[Service Account from console] +NAVERWORKS_PRIVATE_KEY_PATH=/secure/naverworks/private_key.json +``` + +### 2.3 데이터베이스 스키마 + +#### 신규 테이블 (선택적) +```sql +-- NAVER WORKS 전용 매핑 테이블 (필요시) +CREATE TABLE naverworks_user_mapping ( + naverworks_user_id VARCHAR(100) NOT NULL, + naverworks_org_id VARCHAR(100) NOT NULL, + user_id UUID NOT NULL REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (naverworks_user_id, naverworks_org_id) +); + +-- NAVER WORKS 토큰 저장 (Service Account용) +CREATE TABLE naverworks_tokens ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + org_id VARCHAR(100) NOT NULL, + access_token TEXT NOT NULL, + refresh_token TEXT, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +#### 기존 users 테이블 활용 +- `oauth_provider`: "naverworks" +- `oauth_id`: NAVER WORKS userId 또는 sub claim +- `email`: 사용자 이메일 +- `name`: 사용자 이름 + +### 2.4 구현 플로우 + +#### 사용자 로그인 (OAuth 2.0 + OIDC) +``` +1. GET /auth/naverworks/login/?redirect_uri={frontend_url} + - State 생성 (UUID) + - Redis 저장: oauth:state:{state} = {"redirect_uri", "created_at"} + - Redirect to: https://auth.worksmobile.com/oauth2/v2.0/authorize + ?client_id={NAVERWORKS_CLIENT_ID} + &redirect_uri={NAVERWORKS_REDIRECT_URI} + &scope=openid+profile+email + &response_type=code + &state={state} + +2. GET|POST /auth/naverworks/callback?code={code}&state={state} + - State 검증 (Redis 조회 및 삭제) + - Token 교환: + POST https://auth.worksmobile.com/oauth2/v2.0/token + { + "code": code, + "client_id": NAVERWORKS_CLIENT_ID, + "client_secret": NAVERWORKS_CLIENT_SECRET, + "redirect_uri": NAVERWORKS_REDIRECT_URI, + "grant_type": "authorization_code" + } + - Userinfo 조회: + GET https://www.worksapis.com/v1.0/oidc/userinfo + Headers: Authorization: Bearer {access_token} + - User 조회/생성: + - oauth_provider="naverworks" + - oauth_id={sub 또는 userId} + - JWT 생성 + - 임시 코드 생성 → Redis: auth:temp:{temp_code} + - Redirect: {redirect_uri}?code={temp_code} + +3. Frontend: POST /auth/verify + - 임시 코드로 JWT 토큰 수령 +``` + +#### Service Account 인증 (JWT) +``` +1. Private Key 로드 (파일 시스템) +2. JWT Assertion 생성: + - iss: NAVERWORKS_CLIENT_ID + - sub: NAVERWORKS_SERVICE_ACCOUNT + - iat: 현재 시간 + - exp: 현재 시간 + 1시간 +3. Token 요청: + POST https://auth.worksmobile.com/oauth2/v2.0/token + { + "assertion": jwt_assertion, + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "client_id": NAVERWORKS_CLIENT_ID, + "client_secret": NAVERWORKS_CLIENT_SECRET, + "scope": "calendar contact mail user" + } +4. Access Token 캐싱 (Redis, TTL 3600s) +``` + +### 2.5 API 사용 예시 + +#### 캘린더 일정 생성 +```python +async def create_calendar_event(user_id: str, event_data: dict): + token = await get_service_account_token() + + async with httpx.AsyncClient() as client: + response = await client.post( + f"{NAVERWORKS_API_BASE}/users/{user_id}/calendar/events", + headers={"Authorization": f"Bearer {token}"}, + json=event_data + ) + return response.json() +``` + +## 3. 구현 체크리스트 + +### 필수 작업 +- [ ] `auth-server/app/providers/naverworks.py` 생성 +- [ ] `auth-server/app/main.py`에 라우터 등록 +- [ ] `.env` 파일에 환경변수 추가 +- [ ] Redis key 패턴 구현 (oauth:state:*, auth:temp:*) +- [ ] User 모델 매핑 로직 +- [ ] JWT 토큰 생성 및 검증 + +### 선택 작업 +- [ ] NAVER WORKS 전용 테이블 생성 (필요시) +- [ ] Service Account JWT 인증 구현 +- [ ] Refresh Token 처리 로직 +- [ ] 에러 핸들링 및 재시도 로직 + +### Frontend 연동 +- [ ] 로그인 버튼 추가 +- [ ] `/auth/naverworks/login` 리다이렉트 +- [ ] 콜백 처리 및 토큰 저장 + +## 4. 보안 고려사항 + +### 비밀 키 관리 +- **결정필요**: Private Key 저장 위치 (파일 시스템 vs Vault) +- Client Secret은 환경변수로만 관리 +- Private Key 파일 권한 600 설정 필수 +- **확인필요**: 한 번에 하나의 Private Key만 활성화 (콘솔 제약) + +### 도메인 일치 +- **결정필요**: Redirect URL 도메인 통일 + - 콘솔 설정: `auth.robeing.com` + - 현재 Slack: `auth.ro-being.com` + - 하나로 통일 필요 + +### 토큰 보안 +- Access Token은 Redis에 임시 저장 (TTL 설정) +- Refresh Token은 암호화하여 DB 저장 +- Service Account 토큰은 게이트웨이 서버에서만 사용 + +## 5. 테스트 시나리오 + +### 로그인 플로우 테스트 +1. `/auth/naverworks/login` 접속 +2. NAVER WORKS 로그인 페이지 확인 +3. 인증 후 콜백 처리 확인 +4. JWT 토큰 발급 확인 +5. Frontend 리다이렉트 확인 + +### API 호출 테스트 +1. Service Account 토큰 발급 +2. 캘린더 API 호출 테스트 +3. 사용자 정보 조회 테스트 +4. 에러 처리 확인 + +## 6. 참고 자료 + +### 내부 문서 +- Slack OAuth 구현: `auth-server/app/providers/slack.py` +- Gmail OAuth 구현: `auth-server/app/providers/gmail_passport.py` +- NAVER WORKS 설정: `DOCS/ideas/250916_네이버웍스_캘린더_API_연동_가이드.md` + +### 외부 문서 +- [LINE WORKS Developers](https://developers.worksmobile.com) +- [OAuth 2.0 인증 가이드](https://developers.worksmobile.com/kr/docs/auth-oauth) +- [API Reference](https://developers.worksmobile.com/kr/reference/introduction) + +## 7. 구현 우선순위 + +1. **Phase 1** (즉시) + - 환경변수 설정 + - 기본 OAuth 로그인 플로우 + - User 매핑 로직 + +2. **Phase 2** (1주 내) + - Service Account 인증 + - 캘린더 API 연동 + - 에러 핸들링 + +3. **Phase 3** (2주 내) + - Refresh Token 자동 갱신 + - 전체 API 통합 + - 모니터링 및 로깅 + +## 8. 미결 사항 + +### 결정 필요 +- [ ] Redirect URL 도메인 (auth.ro-being.com vs auth.robeing.com) +- [ ] Private Key 저장 방식 (파일 vs Vault) +- [ ] NAVER WORKS 전용 테이블 생성 여부 + +### 확인 필요 +- [ ] NAVER WORKS OIDC userinfo endpoint 정확한 응답 형식 +- [ ] Service Account JWT 서명 알고리즘 +- [ ] Rate Limit 및 쿼터 제한 +- [ ] Private Key 교체 시 기존 토큰 유효성 + +--- +*작성: 2025-09-17 / happybell80* +*다음 업데이트: 구현 완료 후* \ No newline at end of file