mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 12:36:23 +00:00
- 5개 마이크로서비스 API 명세 작성 (User, Meeting, STT, AI, Notification) - OpenAPI 3.0 표준 준수 - 총 47개 API 설계 - 유저스토리 100% 커버리지 - swagger-cli 검증 통과 - 종합 API 설계서 작성 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
532 lines
16 KiB
YAML
532 lines
16 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: User Service API
|
|
description: |
|
|
회의록 작성 및 공유 개선 서비스의 사용자 인증 전용 서비스
|
|
|
|
**핵심 기능:**
|
|
- LDAP 기반 사용자 인증
|
|
- JWT 토큰 발급 및 검증
|
|
- 세션 관리 (Access Token + Refresh Token)
|
|
|
|
**보안:**
|
|
- LDAP 인증 (LDAPS, port 636)
|
|
- JWT Bearer 토큰
|
|
- 계정 잠금 (5회 실패 시 30분)
|
|
- Refresh Token (Redis 저장, 7일 TTL)
|
|
|
|
version: 1.0.0
|
|
contact:
|
|
name: Backend Team
|
|
email: backend@company.com
|
|
|
|
servers:
|
|
- url: https://api.meeting.company.com
|
|
description: Production Server
|
|
- url: https://dev-api.meeting.company.com
|
|
description: Development Server
|
|
- url: http://localhost:8081
|
|
description: Local Development
|
|
|
|
tags:
|
|
- name: Authentication
|
|
description: 사용자 인증 관리 API
|
|
|
|
paths:
|
|
/api/v1/auth/login:
|
|
post:
|
|
summary: 사용자 로그인
|
|
description: |
|
|
LDAP 인증을 통한 사용자 로그인 처리
|
|
|
|
**처리 흐름:**
|
|
1. LDAP 서버에 사용자 인증 요청
|
|
2. 인증 성공 시 사용자 정보 조회
|
|
3. 신규 사용자일 경우 자동 등록
|
|
4. JWT Access Token 및 Refresh Token 발급
|
|
5. Refresh Token을 Redis에 저장 (7일 TTL)
|
|
6. 최종 로그인 일시 업데이트
|
|
|
|
**인증 실패 처리:**
|
|
- 5회 실패 시 계정 30분 잠금
|
|
- 잠금 상태에서는 로그인 불가
|
|
operationId: login
|
|
x-user-story: AFR-USER-010
|
|
x-controller: UserController
|
|
tags:
|
|
- Authentication
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginRequest'
|
|
examples:
|
|
normal:
|
|
summary: 정상 로그인 요청
|
|
value:
|
|
username: "user001"
|
|
password: "P@ssw0rd123!"
|
|
responses:
|
|
'200':
|
|
description: 로그인 성공
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginResponse'
|
|
examples:
|
|
success:
|
|
summary: 로그인 성공 응답
|
|
value:
|
|
accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
tokenType: "Bearer"
|
|
expiresIn: 3600
|
|
user:
|
|
userId: "user001"
|
|
username: "user001"
|
|
name: "홍길동"
|
|
email: "hong@company.com"
|
|
department: "개발팀"
|
|
title: "선임"
|
|
roles:
|
|
- "USER"
|
|
'401':
|
|
$ref: '#/components/responses/UnauthorizedError'
|
|
'403':
|
|
description: 계정 잠금
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
examples:
|
|
accountLocked:
|
|
summary: 계정 잠금 응답
|
|
value:
|
|
error:
|
|
code: "ACCOUNT_LOCKED"
|
|
message: "계정이 잠겼습니다"
|
|
details: "비밀번호 5회 실패로 30분간 계정이 잠겼습니다. 잠금 해제 시간: 2025-10-23T13:30:00Z"
|
|
timestamp: "2025-10-23T13:00:00Z"
|
|
path: "/api/v1/auth/login"
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/api/v1/auth/refresh:
|
|
post:
|
|
summary: Access Token 갱신
|
|
description: |
|
|
Refresh Token을 사용하여 새로운 Access Token 발급
|
|
|
|
**처리 흐름:**
|
|
1. Refresh Token 검증
|
|
2. Redis에서 Refresh Token 존재 확인
|
|
3. 새로운 Access Token 발급
|
|
4. Refresh Token 갱신 (옵션)
|
|
operationId: refreshToken
|
|
x-user-story: AFR-USER-010
|
|
x-controller: UserController
|
|
tags:
|
|
- Authentication
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RefreshTokenRequest'
|
|
examples:
|
|
normal:
|
|
summary: 토큰 갱신 요청
|
|
value:
|
|
refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
responses:
|
|
'200':
|
|
description: 토큰 갱신 성공
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RefreshTokenResponse'
|
|
examples:
|
|
success:
|
|
summary: 토큰 갱신 성공 응답
|
|
value:
|
|
accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
tokenType: "Bearer"
|
|
expiresIn: 3600
|
|
'401':
|
|
$ref: '#/components/responses/UnauthorizedError'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/api/v1/auth/logout:
|
|
post:
|
|
summary: 로그아웃
|
|
description: |
|
|
사용자 로그아웃 처리
|
|
|
|
**처리 흐름:**
|
|
1. Access Token에서 사용자 정보 추출
|
|
2. Redis에서 Refresh Token 삭제
|
|
3. 로그아웃 완료
|
|
operationId: logout
|
|
x-user-story: AFR-USER-010
|
|
x-controller: UserController
|
|
tags:
|
|
- Authentication
|
|
security:
|
|
- BearerAuth: []
|
|
responses:
|
|
'200':
|
|
description: 로그아웃 성공
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LogoutResponse'
|
|
examples:
|
|
success:
|
|
summary: 로그아웃 성공 응답
|
|
value:
|
|
message: "로그아웃되었습니다"
|
|
timestamp: "2025-10-23T14:30:00Z"
|
|
'401':
|
|
$ref: '#/components/responses/UnauthorizedError'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/api/v1/auth/validate:
|
|
get:
|
|
summary: 토큰 검증
|
|
description: |
|
|
JWT Access Token 유효성 검증
|
|
|
|
**검증 항목:**
|
|
- 토큰 서명 검증
|
|
- 토큰 만료 시간 확인
|
|
- 사용자 정보 조회 (옵션)
|
|
|
|
**용도:**
|
|
- API Gateway에서 인증 검증
|
|
- 다른 서비스에서 사용자 정보 조회
|
|
operationId: validateToken
|
|
x-user-story: AFR-USER-010
|
|
x-controller: UserController
|
|
tags:
|
|
- Authentication
|
|
security:
|
|
- BearerAuth: []
|
|
parameters:
|
|
- name: includeUserInfo
|
|
in: query
|
|
description: 사용자 정보 포함 여부
|
|
required: false
|
|
schema:
|
|
type: boolean
|
|
default: false
|
|
responses:
|
|
'200':
|
|
description: 토큰 유효
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ValidateTokenResponse'
|
|
examples:
|
|
valid:
|
|
summary: 토큰 유효 응답
|
|
value:
|
|
valid: true
|
|
userId: "user001"
|
|
username: "user001"
|
|
roles:
|
|
- "USER"
|
|
expiresAt: "2025-10-23T15:00:00Z"
|
|
validWithUserInfo:
|
|
summary: 사용자 정보 포함 응답
|
|
value:
|
|
valid: true
|
|
userId: "user001"
|
|
username: "user001"
|
|
roles:
|
|
- "USER"
|
|
expiresAt: "2025-10-23T15:00:00Z"
|
|
user:
|
|
userId: "user001"
|
|
username: "user001"
|
|
name: "홍길동"
|
|
email: "hong@company.com"
|
|
department: "개발팀"
|
|
title: "선임"
|
|
'401':
|
|
$ref: '#/components/responses/UnauthorizedError'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
components:
|
|
securitySchemes:
|
|
BearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: JWT Bearer Token
|
|
|
|
schemas:
|
|
LoginRequest:
|
|
type: object
|
|
required:
|
|
- username
|
|
- password
|
|
properties:
|
|
username:
|
|
type: string
|
|
description: 사용자명 (사번)
|
|
minLength: 3
|
|
maxLength: 50
|
|
example: "user001"
|
|
password:
|
|
type: string
|
|
description: 비밀번호
|
|
format: password
|
|
minLength: 8
|
|
maxLength: 100
|
|
example: "P@ssw0rd123!"
|
|
|
|
LoginResponse:
|
|
type: object
|
|
required:
|
|
- accessToken
|
|
- refreshToken
|
|
- tokenType
|
|
- expiresIn
|
|
- user
|
|
properties:
|
|
accessToken:
|
|
type: string
|
|
description: JWT Access Token
|
|
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMDAxIiwidXNlcm5hbWUiOiJ1c2VyMDAxIiwicm9sZXMiOlsiVVNFUiJdLCJleHAiOjE3MzAwMDAwMDB9.signature"
|
|
refreshToken:
|
|
type: string
|
|
description: Refresh Token (7일 유효)
|
|
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMDAxIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3MzA2MDQ4MDB9.signature"
|
|
tokenType:
|
|
type: string
|
|
description: 토큰 타입
|
|
enum:
|
|
- Bearer
|
|
example: "Bearer"
|
|
expiresIn:
|
|
type: integer
|
|
description: Access Token 만료 시간 (초)
|
|
example: 3600
|
|
user:
|
|
$ref: '#/components/schemas/UserInfo'
|
|
|
|
RefreshTokenRequest:
|
|
type: object
|
|
required:
|
|
- refreshToken
|
|
properties:
|
|
refreshToken:
|
|
type: string
|
|
description: Refresh Token
|
|
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
|
|
RefreshTokenResponse:
|
|
type: object
|
|
required:
|
|
- accessToken
|
|
- refreshToken
|
|
- tokenType
|
|
- expiresIn
|
|
properties:
|
|
accessToken:
|
|
type: string
|
|
description: 새로운 JWT Access Token
|
|
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
refreshToken:
|
|
type: string
|
|
description: 새로운 Refresh Token (옵션)
|
|
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
tokenType:
|
|
type: string
|
|
description: 토큰 타입
|
|
enum:
|
|
- Bearer
|
|
example: "Bearer"
|
|
expiresIn:
|
|
type: integer
|
|
description: Access Token 만료 시간 (초)
|
|
example: 3600
|
|
|
|
LogoutResponse:
|
|
type: object
|
|
required:
|
|
- message
|
|
- timestamp
|
|
properties:
|
|
message:
|
|
type: string
|
|
description: 로그아웃 완료 메시지
|
|
example: "로그아웃되었습니다"
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
description: 로그아웃 시간
|
|
example: "2025-10-23T14:30:00Z"
|
|
|
|
ValidateTokenResponse:
|
|
type: object
|
|
required:
|
|
- valid
|
|
- userId
|
|
- username
|
|
- roles
|
|
- expiresAt
|
|
properties:
|
|
valid:
|
|
type: boolean
|
|
description: 토큰 유효 여부
|
|
example: true
|
|
userId:
|
|
type: string
|
|
description: 사용자 ID
|
|
example: "user001"
|
|
username:
|
|
type: string
|
|
description: 사용자명
|
|
example: "user001"
|
|
roles:
|
|
type: array
|
|
description: 사용자 권한 목록
|
|
items:
|
|
type: string
|
|
example:
|
|
- "USER"
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
description: 토큰 만료 시간
|
|
example: "2025-10-23T15:00:00Z"
|
|
user:
|
|
$ref: '#/components/schemas/UserInfo'
|
|
description: 사용자 정보 (includeUserInfo=true 시)
|
|
|
|
UserInfo:
|
|
type: object
|
|
required:
|
|
- userId
|
|
- username
|
|
- name
|
|
- email
|
|
properties:
|
|
userId:
|
|
type: string
|
|
description: 사용자 ID
|
|
example: "user001"
|
|
username:
|
|
type: string
|
|
description: 사용자명 (사번)
|
|
example: "user001"
|
|
name:
|
|
type: string
|
|
description: 이름
|
|
example: "홍길동"
|
|
email:
|
|
type: string
|
|
format: email
|
|
description: 이메일
|
|
example: "hong@company.com"
|
|
department:
|
|
type: string
|
|
description: 부서
|
|
example: "개발팀"
|
|
title:
|
|
type: string
|
|
description: 직급
|
|
example: "선임"
|
|
roles:
|
|
type: array
|
|
description: 권한 목록
|
|
items:
|
|
type: string
|
|
example:
|
|
- "USER"
|
|
|
|
ErrorResponse:
|
|
type: object
|
|
required:
|
|
- error
|
|
properties:
|
|
error:
|
|
type: object
|
|
required:
|
|
- code
|
|
- message
|
|
- timestamp
|
|
- path
|
|
properties:
|
|
code:
|
|
type: string
|
|
description: 에러 코드
|
|
example: "AUTHENTICATION_FAILED"
|
|
message:
|
|
type: string
|
|
description: 에러 메시지
|
|
example: "인증에 실패했습니다"
|
|
details:
|
|
type: string
|
|
description: 상세 에러 정보
|
|
example: "사용자명 또는 비밀번호가 올바르지 않습니다"
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
description: 에러 발생 시간
|
|
example: "2025-10-23T12:00:00Z"
|
|
path:
|
|
type: string
|
|
description: 요청 경로
|
|
example: "/api/v1/auth/login"
|
|
|
|
responses:
|
|
UnauthorizedError:
|
|
description: 인증 실패
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
examples:
|
|
authenticationFailed:
|
|
summary: 인증 실패
|
|
value:
|
|
error:
|
|
code: "AUTHENTICATION_FAILED"
|
|
message: "인증에 실패했습니다"
|
|
details: "사용자명 또는 비밀번호가 올바르지 않습니다"
|
|
timestamp: "2025-10-23T12:00:00Z"
|
|
path: "/api/v1/auth/login"
|
|
invalidToken:
|
|
summary: 유효하지 않은 토큰
|
|
value:
|
|
error:
|
|
code: "INVALID_TOKEN"
|
|
message: "유효하지 않은 토큰입니다"
|
|
details: "토큰이 만료되었거나 형식이 올바르지 않습니다"
|
|
timestamp: "2025-10-23T12:00:00Z"
|
|
path: "/api/v1/auth/validate"
|
|
|
|
InternalServerError:
|
|
description: 서버 오류
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
examples:
|
|
serverError:
|
|
summary: 서버 오류
|
|
value:
|
|
error:
|
|
code: "INTERNAL_SERVER_ERROR"
|
|
message: "서버 오류가 발생했습니다"
|
|
details: "일시적인 오류입니다. 잠시 후 다시 시도해주세요"
|
|
timestamp: "2025-10-23T12:00:00Z"
|
|
path: "/api/v1/auth/login"
|