Repository navigation
[Auth & Security] JWT 인증·사업장 권한·멀티테넌시 구현 #4
Copy link
Copy link
Closed
Labels
area:serverSpring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외Spring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외priority:P0MVP 진행을 막는 최우선 핵심 작업MVP 진행을 막는 최우선 핵심 작업security:privacy개인정보·접근권한·토큰·보안 영향이 있는 작업개인정보·접근권한·토큰·보안 영향이 있는 작업type:feature사용자 또는 Agent가 사용하는 기능 개발사용자 또는 Agent가 사용하는 기능 개발
Description
한 줄 목표
사업장 사용자가 안전하게 로그인하고, 자신의 사업장 데이터만 역할에 맞게 사용하도록 JWT 인증·권한·Refresh Token 수명주기를 구현합니다.
현재 진행 상태
- 구현 PR: feat: Auth·Company 로그인 구현 및 DB 마이그레이션 정합성 보완 #33(로그인·JWT·ActorContext, 병합됨) + feat: Refresh Token 회전·로그아웃과 안전한 데모 인증 기반 구현 #35(재발급·로그아웃·Seed, Ready for review)
- 담당자: @hywznn
- 현재 단계: feat: Refresh Token 회전·로그아웃과 안전한 데모 인증 기반 구현 #35 PostgreSQL CI 통과, 팀원 리뷰 대기
- 완료 시점: #35가 CI·리뷰를 통과해
main에 병합될 때 이 Issue가 자동으로 닫힙니다.
초보자 설명: Access Token은 API를 잠깐 사용하는 출입증이고, Refresh Token은 출입증을 다시 발급받는 수단입니다. 서버는 Refresh Token을 매번 새 값으로 교체하며, 이미 쓴 값이 다시 오면 해당 로그인 묶음을 모두 폐기합니다.
공식 API
/api/v1을 생략한 /auth/** 별칭은 만들지 않습니다.
| API | 인증 | 하는 일 | 주요 응답 |
|---|---|---|---|
POST /api/v1/auth/login |
Public, JSON email/password | 로그인하고 Access Token과 HttpOnly Refresh 쿠키 발급 | 200, 실패 401 INVALID_CREDENTIALS |
POST /api/v1/auth/refresh |
Refresh 쿠키만 | 기존 쿠키를 한 번 사용하고 Access/Refresh Token 회전 | 200, 실패 401 INVALID_REFRESH_TOKEN |
POST /api/v1/auth/logout |
선택적 Refresh 쿠키 | 현재 로그인 family 폐기와 쿠키 삭제 | 항상 204; DB 장애는 성공으로 숨기지 않음 |
GET /api/v1/auth/me |
Bearer Access Token | 현재 user_id, company_id, roles 확인 | 200, 실패 401 |
구현 체크리스트
- Company, UserAccount, RefreshToken 모델과 V2 Flyway migration
- 비밀번호 BCrypt 단방향 hash 저장
- 로그인과 JWT Access Token 발급·검증
- Refresh Token 원문 비저장, SHA-256 hash 저장
- Refresh Token rotation과 이전 토큰 사용 처리
- 재사용 탐지 시 token family 전체 폐기
- 멱등 로그아웃과 HttpOnly 쿠키 삭제
- JWT에서 user_id, company_id, roles를 ActorContext로 구성
- ADMIN·HR·VIEWER 역할 검사
- user_id + company_id 조회와 타 사업장 리소스 404 은닉
- privacy-safe 인증 감사 이벤트와 AuthAuditPort 정의
- 기본 비활성·Secret 필수인 데모 Company·ADMIN Seed 제공
- OpenAPI·README·ADR-0002·환경변수 예시 갱신
감사 이벤트 경계
#35는 로그인 성공·실패, 재발급, 재사용 탐지, token family 폐기, 로그아웃 action을 원본 토큰·email·비밀번호 없이 안전한 ID로 정의합니다. 현재 adapter는 request_id가 포함된 진단 로그입니다. append-only 영구 감사 테이블과 검색 API는 소유 Issue #11에서 이 port에 연결합니다.
검증 체크리스트
- 정상 로그인·JWT claims·
/auth/me - 잘못된 email과 비밀번호가 동일한 외부 오류
- VIEWER 쓰기 차단, HR 쓰기 허용
- 다른 사업장 리소스 404 은닉
- 정상 Refresh와 교체 chain·family 검증
- 누락·변조·미존재·폐기·재사용 토큰의 동일한 401
- 재사용 뒤 새 교체 토큰까지 폐기
- 로그아웃 반복 호출과 서로 다른 로그인 family 격리
- 계정·사업장 정지 시 Refresh 차단과 family 폐기
- 데모 Seed 멱등성·BCrypt 저장·Secret 비노출
- 로컬
./gradlew clean test와./gradlew build - feat: Refresh Token 회전·로그아웃과 안전한 데모 인증 기반 구현 #35 GitHub Actions의 실제 PostgreSQL 동시성 테스트
- 팀원 리뷰와
main병합
보안 규칙
- Refresh Token은 JSON·일반 로그·감사 이벤트에 넣지 않고 HttpOnly 쿠키로만 전달합니다.
- 쿠키는 운영에서 Secure이며, CSRF/Origin 보호 전에는
SameSite=None을 금지하고 Strict/Lax만 허용합니다. - 같은 쿠키의 동시 Refresh는 보수적으로 재사용으로 판단할 수 있으므로 Client는 single-flight로 한 번만 호출합니다.
- 로그아웃 뒤 이미 발급된 stateless Access Token은 기본 TTL 기준 최대 15분 유효할 수 있어 Client가 즉시 삭제합니다.
- Seed는 기본 비활성이며
DEMO_SEED_ADMIN_PASSWORD를 실행 환경 Secret으로 줄 때만 동작합니다. - JWT Secret, 초기 비밀번호, 실제 토큰을 GitHub·Notion·로그에 올리지 않습니다.
이번 Issue에서 하지 않는 것
- 소셜 로그인, 근로자 로그인, MFA, SSO
- 즉시 Access Token deny-list 또는 session version
- 영구 AuditEvent schema·검색 API: [Approval & Audit] 승인·반려·외부제출·증빙·완료·감사 로그 구현 #11
- PostgreSQL RLS 도입: [Security] PostgreSQL RLS 기반 사업장 2차 격리 안전 도입 #34
- Worker·Task 등 후속 aggregate의 tenant query: 각 소유 Issue에서 구현
선행·후속 관계
- 기반 결정: [Architecture] 저장소 경계·모듈·API·이벤트 계약 ADR 작성 #23
- 구현 PR: feat: Auth·Company 로그인 구현 및 DB 마이그레이션 정합성 보완 #33, feat: Refresh Token 회전·로그아웃과 안전한 데모 인증 기반 구현 #35
- RLS 후속 검토: [Security] PostgreSQL RLS 기반 사업장 2차 격리 안전 도입 #34
- 후속 기능: [Worker] 근로자 기본정보·서류 메타데이터 API 구현 #5 Worker·Document, [Task Workflow] 업무카드·체크리스트·상태 전이 구현 #6 Task Workflow, [Worker Link] 로그인 없는 근로자 보안 링크와 응답 구현 #7 Worker Link, [Approval & Audit] 승인·반려·외부제출·증빙·완료·감사 로그 구현 #11 Approval·Audit
- 상위 목표: [EPIC] FOWOCO Server Roadmap · Controlled AI Workflow MVP #2
용어 도움말
- JWT Access Token: API 요청자가 누구인지 짧은 시간 동안 증명하는 문자열
- Refresh Token rotation: 재발급할 때 기존 Refresh Token을 폐기하고 새 값으로 교체하는 방식
- Token family: 한 번의 로그인에서 이어진 Refresh Token 교체 묶음
- Single-flight: 동시에 여러 재발급을 보내지 않고 한 요청의 결과를 함께 기다리는 Client 방식
- 멀티테넌시: 여러 사업장이 한 서버를 쓰되 서로의 데이터를 볼 수 없게 분리하는 구조
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:serverSpring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외Spring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외priority:P0MVP 진행을 막는 최우선 핵심 작업MVP 진행을 막는 최우선 핵심 작업security:privacy개인정보·접근권한·토큰·보안 영향이 있는 작업개인정보·접근권한·토큰·보안 영향이 있는 작업type:feature사용자 또는 Agent가 사용하는 기능 개발사용자 또는 Agent가 사용하는 기능 개발
Activity
[-][Security] JWT 인증·사업장 권한·멀티테넌시 구현[/-][+][Auth & Security] JWT 인증·사업장 권한·멀티테넌시 구현[/+]chore: scaffold auth and company packages