335 lines
10 KiB
Markdown
335 lines
10 KiB
Markdown
# 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 처리 완료** (2025-09-17):
|
|
- Git에 임시 commit 후 서버 전송 완료
|
|
- 서버 51123에 안전하게 저장
|
|
- Git에서 즉시 삭제 (commit a4a2b9c)
|
|
- 서버 경로: `/secure/naverworks/private_20250917185550.key`
|
|
- Client Secret은 환경변수로만 관리
|
|
- Private Key 파일 권한 600 설정 필수
|
|
- **확인필요**: 한 번에 하나의 Private Key만 활성화 (콘솔 제약)
|
|
|
|
### 도메인 일치
|
|
- **결정 완료**: `auth.ro-being.com` 사용
|
|
- 콘솔 설정 변경 필요: auth.robeing.com → auth.ro-being.com
|
|
- 기존 Slack과 통일
|
|
|
|
### 토큰 보안
|
|
- 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. 미결 사항
|
|
|
|
### 결정 완료
|
|
- [x] Redirect URL 도메인: `auth.ro-being.com` 사용
|
|
- [x] Private Key: 서버 51123 `/secure/naverworks/` 저장
|
|
- [x] 테이블: `naverworks_token` (team 스키마, 단수형)
|
|
|
|
### 확인 필요
|
|
- [ ] NAVER WORKS OIDC userinfo endpoint 정확한 응답 형식
|
|
- [ ] Service Account JWT 서명 알고리즘
|
|
- [ ] Rate Limit 및 쿼터 제한
|
|
- [ ] Private Key 교체 시 기존 토큰 유효성
|
|
|
|
---
|
|
*작성: 2025-09-17 / happybell80*
|
|
*다음 업데이트: 구현 완료 후* |