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"