341 lines
11 KiB
Markdown
341 lines
11 KiB
Markdown
# 카카오 소셜 로그인 구현 가이드
|
|
|
|
## 목차
|
|
|
|
1. [개요](#1-개요)
|
|
2. [인증 흐름](#2-인증-흐름)
|
|
3. [User 모델 구조](#3-user-모델-구조)
|
|
4. [API 엔드포인트](#4-api-엔드포인트)
|
|
5. [환경 설정](#5-환경-설정)
|
|
6. [에러 처리](#6-에러-처리)
|
|
|
|
---
|
|
|
|
## 1. 개요
|
|
|
|
CastAD는 카카오 소셜 로그인만 지원하며, 인증 후 자체 JWT 토큰을 발급합니다.
|
|
|
|
### 인증 방식
|
|
|
|
| 항목 | 설명 |
|
|
|------|------|
|
|
| 소셜 로그인 | 카카오 OAuth 2.0 |
|
|
| 자체 인증 | JWT (Access Token + Refresh Token) |
|
|
| 카카오 토큰 저장 | X (1회 검증 후 폐기) |
|
|
|
|
---
|
|
|
|
## 2. 인증 흐름
|
|
|
|
### 2.1 전체 흐름도
|
|
|
|
```
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
│ Client │ │ Backend │ │ Kakao │ │ DB │
|
|
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
|
|
│ │ │ │
|
|
│ 1. 로그인 요청 │ │ │
|
|
│──────────────>│ │ │
|
|
│ │ │ │
|
|
│ 2. 카카오 로그인 URL 반환 │ │
|
|
│<──────────────│ │ │
|
|
│ │ │ │
|
|
│ 3. 카카오 로그인 페이지로 이동 │ │
|
|
│──────────────────────────────>│ │
|
|
│ │ │ │
|
|
│ 4. 사용자 인증 후 code 반환 │ │
|
|
│<──────────────────────────────│ │
|
|
│ │ │ │
|
|
│ 5. code 전달 │ │ │
|
|
│──────────────>│ │ │
|
|
│ │ │ │
|
|
│ │ 6. code로 토큰 요청 │
|
|
│ │──────────────>│ │
|
|
│ │ │ │
|
|
│ │ 7. access_token 반환 │
|
|
│ │<──────────────│ │
|
|
│ │ │ │
|
|
│ │ 8. 사용자 정보 조회 │
|
|
│ │──────────────>│ │
|
|
│ │ │ │
|
|
│ │ 9. 사용자 정보 반환 │
|
|
│ │<──────────────│ │
|
|
│ │ │ │
|
|
│ │ 10. kakao_id로 회원 조회 │
|
|
│ │──────────────────────────────>│
|
|
│ │ │ │
|
|
│ │ 11. 회원 정보 반환 (없으면 생성) │
|
|
│ │<──────────────────────────────│
|
|
│ │ │ │
|
|
│ 12. 자체 JWT 발급 및 반환 │ │
|
|
│<──────────────│ │ │
|
|
│ │ │ │
|
|
```
|
|
|
|
### 2.2 단계별 설명
|
|
|
|
| 단계 | 설명 | 관련 API |
|
|
|------|------|----------|
|
|
| 1-2 | 클라이언트가 로그인 요청, 백엔드가 카카오 인증 URL 생성 | `GET /user/auth/kakao/login` |
|
|
| 3-4 | 사용자가 카카오에서 로그인, 인가 코드(code) 발급 | 카카오 OAuth |
|
|
| 5-9 | 백엔드가 code로 카카오 토큰/사용자정보 획득 | 카카오 API |
|
|
| 10-11 | DB에서 회원 조회, 없으면 신규 가입 | 내부 처리 |
|
|
| 12 | 자체 JWT 토큰 발급 후 클라이언트에 반환 | `POST /user/auth/kakao/callback` |
|
|
|
|
---
|
|
|
|
## 3. User 모델 구조
|
|
|
|
### 3.1 테이블 스키마
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ user │
|
|
├─────────────────────┬───────────────┬───────────────────────┤
|
|
│ Column │ Type │ Description │
|
|
├─────────────────────┼───────────────┼───────────────────────┤
|
|
│ id │ BIGINT (PK) │ 고유 식별자 (자동증가) │
|
|
│ kakao_id │ BIGINT (UQ) │ 카카오 회원번호 │
|
|
│ email │ VARCHAR(255) │ 이메일 (선택) │
|
|
│ nickname │ VARCHAR(100) │ 닉네임 (선택) │
|
|
│ profile_image_url │ VARCHAR(2048) │ 프로필 이미지 URL │
|
|
│ thumbnail_image_url │ VARCHAR(2048) │ 썸네일 이미지 URL │
|
|
│ is_active │ BOOLEAN │ 계정 활성화 상태 │
|
|
│ is_admin │ BOOLEAN │ 관리자 권한 │
|
|
│ last_login_at │ DATETIME │ 마지막 로그인 일시 │
|
|
│ created_at │ DATETIME │ 생성 일시 │
|
|
│ updated_at │ DATETIME │ 수정 일시 │
|
|
└─────────────────────┴───────────────┴───────────────────────┘
|
|
```
|
|
|
|
### 3.2 카카오 API 응답 매핑
|
|
|
|
```json
|
|
// 카카오 API 응답 예시
|
|
{
|
|
"id": 1234567890,
|
|
"kakao_account": {
|
|
"email": "user@kakao.com",
|
|
"profile": {
|
|
"nickname": "홍길동",
|
|
"profile_image_url": "https://k.kakaocdn.net/.../profile.jpg",
|
|
"thumbnail_image_url": "https://k.kakaocdn.net/.../thumb.jpg"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
| User 필드 | 카카오 응답 경로 |
|
|
|-----------|-----------------|
|
|
| `kakao_id` | `id` |
|
|
| `email` | `kakao_account.email` |
|
|
| `nickname` | `kakao_account.profile.nickname` |
|
|
| `profile_image_url` | `kakao_account.profile.profile_image_url` |
|
|
| `thumbnail_image_url` | `kakao_account.profile.thumbnail_image_url` |
|
|
|
|
---
|
|
|
|
## 4. API 엔드포인트
|
|
|
|
### 4.1 카카오 로그인 URL 요청
|
|
|
|
```
|
|
GET /user/auth/kakao/login
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"auth_url": "https://kauth.kakao.com/oauth/authorize?client_id=...&redirect_uri=...&response_type=code"
|
|
}
|
|
```
|
|
|
|
### 4.2 카카오 콜백 (로그인/가입 처리)
|
|
|
|
```
|
|
POST /user/auth/kakao/callback
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"code": "인가코드"
|
|
}
|
|
```
|
|
|
|
**Response (성공):**
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"token_type": "Bearer",
|
|
"expires_in": 3600,
|
|
"user": {
|
|
"id": 1,
|
|
"nickname": "홍길동",
|
|
"email": "user@kakao.com",
|
|
"profile_image_url": "https://...",
|
|
"is_new_user": false
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.3 토큰 갱신
|
|
|
|
```
|
|
POST /user/auth/refresh
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"token_type": "Bearer",
|
|
"expires_in": 3600
|
|
}
|
|
```
|
|
|
|
### 4.4 로그아웃
|
|
|
|
```
|
|
POST /user/auth/logout
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
### 4.5 모든 기기에서 로그아웃
|
|
|
|
```
|
|
POST /user/auth/logout/all
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
### 4.6 내 정보 조회
|
|
|
|
```
|
|
GET /user/auth/me
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"kakao_id": 1234567890,
|
|
"nickname": "홍길동",
|
|
"email": "user@kakao.com",
|
|
"profile_image_url": "https://...",
|
|
"is_admin": false,
|
|
"created_at": "2026-01-14T16:00:00"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 환경 설정
|
|
|
|
### 5.1 카카오 개발자 설정
|
|
|
|
1. [카카오 개발자 콘솔](https://developers.kakao.com) 접속
|
|
2. 애플리케이션 생성
|
|
3. 플랫폼 > Web 사이트 도메인 등록
|
|
4. 카카오 로그인 > Redirect URI 등록
|
|
5. 동의항목 > 필요한 정보 설정
|
|
|
|
### 5.2 .env 설정
|
|
|
|
```env
|
|
# 카카오 OAuth
|
|
KAKAO_CLIENT_ID=your_rest_api_key
|
|
KAKAO_CLIENT_SECRET=your_client_secret # 선택
|
|
KAKAO_REDIRECT_URI=https://your-domain.com/api/v1/auth/kakao/callback
|
|
|
|
# JWT
|
|
JWT_SECRET=your-super-secret-key-min-32-characters
|
|
JWT_ALGORITHM=HS256
|
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
|
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
|
|
```
|
|
|
|
### 5.3 config.py 설정
|
|
|
|
```python
|
|
class KakaoSettings(BaseSettings):
|
|
KAKAO_CLIENT_ID: str = Field(...)
|
|
KAKAO_CLIENT_SECRET: str = Field(default="")
|
|
KAKAO_REDIRECT_URI: str = Field(...)
|
|
|
|
model_config = _base_config
|
|
|
|
|
|
class JWTSettings(BaseSettings):
|
|
JWT_SECRET: str = Field(...)
|
|
JWT_ALGORITHM: str = Field(default="HS256")
|
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(default=60)
|
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = Field(default=7)
|
|
|
|
model_config = _base_config
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 에러 처리
|
|
|
|
### 6.1 에러 코드 정의
|
|
|
|
| HTTP Status | Error Code | 설명 |
|
|
|-------------|------------|------|
|
|
| 400 | `INVALID_CODE` | 유효하지 않은 인가 코드 |
|
|
| 400 | `KAKAO_AUTH_FAILED` | 카카오 인증 실패 |
|
|
| 401 | `TOKEN_EXPIRED` | 토큰 만료 |
|
|
| 401 | `INVALID_TOKEN` | 유효하지 않은 토큰 |
|
|
| 401 | `TOKEN_REVOKED` | 취소된 토큰 |
|
|
| 403 | `USER_INACTIVE` | 비활성화된 계정 |
|
|
| 403 | `ADMIN_REQUIRED` | 관리자 권한 필요 |
|
|
| 404 | `USER_NOT_FOUND` | 사용자 없음 |
|
|
| 500 | `KAKAO_API_ERROR` | 카카오 API 오류 |
|
|
|
|
### 6.2 에러 응답 형식
|
|
|
|
```json
|
|
{
|
|
"detail": {
|
|
"code": "TOKEN_EXPIRED",
|
|
"message": "토큰이 만료되었습니다. 다시 로그인해주세요."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 부록: 파일 구조
|
|
|
|
```
|
|
app/user/
|
|
├── __init__.py
|
|
├── models.py # User, RefreshToken 모델
|
|
├── exceptions.py # 사용자 정의 예외
|
|
├── schemas/
|
|
│ ├── __init__.py
|
|
│ └── user_schema.py # Pydantic 스키마
|
|
├── services/
|
|
│ ├── __init__.py
|
|
│ ├── auth.py # 인증 서비스
|
|
│ ├── jwt.py # JWT 유틸리티
|
|
│ └── kakao.py # 카카오 OAuth 클라이언트
|
|
├── dependencies/
|
|
│ ├── __init__.py
|
|
│ └── auth.py # 인증 의존성 (get_current_user 등)
|
|
└── api/
|
|
└── routers/
|
|
└── v1/
|
|
├── __init__.py
|
|
└── auth.py # 인증 API 라우터
|
|
```
|