개요 — 무엇을 하나
InfraGuard 는 두 개의 인증 평면을 가집니다.
- 웹 콘솔 = 세션 로그인 — 운영자가
login_id+password로 로그인하면 커스텀 세션이 만들어집니다(AuthController). 이후 모든 콘솔 페이지는 미들웨어infr.auth(InfrAuth)가 세션을 검사하고, 매 요청마다 등급·이름·언어를 DB 에서 다시 읽어 세션 사본을 재동기화합니다. - 외부 인제스트 API = 무상태 API 키(Bearer) — 외부 앱이 인시던트·백업 리포트·보안 스캔을 보낼 때는 세션이 아니라 API 키 토큰(
Authorization: Bearer …또는X-Infr-Api-Key)으로 인증합니다(미들웨어infr.apikey). 세션·CSRF 를 쓰지 않습니다.
핵심 특징:
- 세션 재동기화 — 관리자가 사용자 등급·이름·언어를 바꾸면 재로그인 없이 다음 요청부터 즉시 반영됩니다.
- URL 레벨 강제 — 메뉴 숨김만으로 접근을 막지 않습니다.
/security·/backup·/admin등은 라우트 그룹에서 등급 미들웨어(infr.level:N)로 직접 URL 접근까지 차단합니다. - 파괴적 작업 = 등급 재확인 + 감사 — 차단·복원·설정 변경 등은 쓰기 등급 게이트 +
infr_audit_log기록 + UI 확인으로 보호합니다.
언제 쓰나
- 콘솔에 처음 접속하거나, 갑자기
/login으로 튕겨 나갔을 때 원인을 이해하고 싶을 때. - 외부 Laravel 앱을 인시던트 인제스트 API나 백업 리포터에 연결하며 어떤 헤더로 인증하는지 알아야 할 때.
- 관리자가 등급/이름/언어를 바꿨는데 왜 재로그인이 필요 없는지 설명해야 할 때.
화면 구성
로그인 화면(/login)은 단순한 카드 하납니다.
| 요소 | 설명 |
|---|---|
| 브랜드·태그라인 | Infra**Guard** 로고 + "융합 인프라 운영 · 방어 · 복원력 콘솔" |
| 로그인 ID | login_id 입력. autocomplete=username, 자동 포커스 |
| 비밀번호 | password 입력 + 눈 아이콘으로 표시/숨김 토글 |
| 오류 배너 | 인증 실패 시 "입력한 자격 증명이 일치하지 않습니다." 표시 |
숨은 필드 next |
로그인 후 돌아갈 경로(반드시 / 로 시작해야 함) |
| 제출 버튼 | "로그인 →" |
로그인 폼은 표준 Blade
@csrf토큰을 포함합니다. 콘솔은 세션 + CSRF 로 보호되며, 별도의 Bearer 토큰을 UI 에서 요구하지 않습니다.
사용법
로그인
- 브라우저에서
/login으로 이동합니다(이미 로그인돼 있으면 자동으로/로 보내집니다). 로그인 ID와비밀번호를 입력합니다.- 검증 규칙:
login_id최대 60자,password4~120자.
- 검증 규칙:
- 로그인 을 누르면 서버가
infr_user에서login_id+is_close=0조건으로 계정을 찾고 비밀번호를 검증합니다. - 성공하면 세션이 재생성(
session()->regenerate())되고next(없으면/)로 이동합니다.
로그인이 성공하면 아래 세션 키가 채워집니다.
| 세션 키 | 값 |
|---|---|
infr_user_id |
infr_user.id (인증 여부의 기준) |
infr_login_id |
로그인 ID |
infr_user_level |
user_level (정수) — RBAC 등급 |
infr_name |
표시 이름 |
infr_lang |
표시 언어(없으면 ko) |
또한 last_log 컬럼이 현재 시각으로 갱신됩니다.
로그아웃
- 상단바 사용자 드롭다운의 로그아웃(또는
POST /logout). - 서버는
infr_user_id를 지우고, 세션을 무효화(invalidate)하고, CSRF 토큰을 재생성한 뒤/login으로 보냅니다.
세션 유지 & 매 요청 재동기화
콘솔의 모든 페이지는 infr.auth(InfrAuth)를 통과하며, 매 요청마다 다음을 수행합니다.
- 세션의
infr_user_id로 사용자를 다시 조회합니다(핫패스라 컬럼 최소: id·login_id·name·email·user_level·role_id·team_id·lang·avatar·is_close). - 계정이 없거나
is_close=1(비활성)이면 세션을 파기하고/login(또는 JSON 401)로 보냅니다. infr_user_level·infr_name·infr_lang을 DB 값으로 덮어써 재동기화합니다 → 관리자가 등급/이름/언어를 바꿔도 재로그인 없이 다음 요청부터 반영.- 사용자 모델을 요청 속성
infr_user에 저장하고, 표시 언어를app()->setLocale()로 설정합니다. OpsPolicy::markPresence()로 프레즌스를 기록합니다(운영 정책 economy 모드가 "지금 콘솔 사용자 있음"을 판정해 DB 수집을 게이팅하는 데 사용 — 운영 정책 참고).
세션 수명은 SESSION_LIFETIME(기본 120분)을 따릅니다.
콘솔 로그인에는 별도의 "로그인 유지(remember-me)" 체크박스가 없습니다. 세션은 위 세션 수명 동안 유지되며, 만료되면 다시 로그인해야 합니다.
외부 인제스트 API 인증(Bearer 키)
외부 앱이 /api/v1/* 로 데이터를 보낼 때는 세션이 아니라 API 키로 인증합니다(무상태 · 세션·CSRF 없음).
| 방법 | 헤더 예시 |
|---|---|
| Bearer | Authorization: Bearer YOUR_KEY |
| 커스텀 헤더 | X-Infr-Api-Key: YOUR_KEY |
동작:
- 서버는 받은 토큰을 해시로 변환해
infr_api_key.key_hash(활성 키만)와 상수시간 비교합니다. 평문 키는 저장하지 않습니다. - 키별 분당 120회 레이트리밋. 초과 시
429 rate_limited+Retry-After헤더. - 매 호출마다
last_used_at·last_used_ip·use_count를 갱신합니다.
주요 엔드포인트:
# 연결·키 확인
curl -sS https://infraguard.example.com/api/v1/ping \
-H "Authorization: Bearer YOUR_KEY"
# 인시던트 자동 등록(디둡 · source=pulse)
curl -sS -X POST https://infraguard.example.com/api/v1/incidents \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"NPE in checkout","fingerprint":"abc123","severity":"crit"}'
키 발급·회수·연동 코드는 Administration ▸ API 발급에서 관리합니다(평문 키는 발급 시 1회만 노출).
Step-up — 파괴적 작업 보호
차단·해제·복원·설정 변경 같은 위험 작업은 비밀번호 재입력 대신 다음 3중 방어로 보호합니다.
- 등급 재확인 — 쓰기 작업은 최소 쓰기 등급(레벨 2 이상), 관리 작업은 관리자(레벨 10)를 서버측에서 다시 검사합니다.
- 감사 로그 — 작업 전/후 값과 행위자·IP 를 감사 로그(
infr_audit_log)에 남깁니다. - UI 확인 — 화면에서 확인 대화상자(step-up)를 거칩니다.
데이터 소스
| 대상 | 용도 |
|---|---|
infr_user |
운영자 계정. 컬럼: login_id·password·user_level·name·email·lang·is_close·role_id·team_id·last_log·avatar |
| 세션 저장소(Redis) | infr_user_id 등 세션 키(REDIS_PREFIX infr_) |
infr_api_key |
인제스트 API 키의 해시(key_hash)·is_active·use_count·last_used_at/ip |
infr_audit_log |
파괴적·보안 작업 감사 기록 |
infr_user.password컬럼은 절대 로그·URL·쿠키에 노출하지 않습니다. API 키 평문은 발급 시점에만 표시되고 DB 에는 해시만 저장됩니다.
권한
/login·/logout— 누구나 접근 가능(guest).- 콘솔 전체 — 유효한 세션 필요(
infr.auth). 세션이 없으면 페이지 요청은/login리다이렉트, AJAX 요청은401 unauthenticated.
URL 레벨 게이트(로그인 이후 추가 판정):
| 영역 | 미들웨어 | 최소 등급 |
|---|---|---|
| 콘솔 일반(Overview·Infrastructure·Assets 읽기) | infr.auth |
로그인만(1) |
/security · /backup |
infr.level:2 |
2 |
/admin |
infr.level:10 |
10 |
/horizon · /pulse |
infr.admin |
10 |
| 개발 섹션 | infr.dev |
9 |
등급 티어: demo=1 · staff=5 · admin=10. 자세한 등급 정책은 RBAC 등급을 참고합니다.
문제해결 · FAQ
로그인해도 계속 /login 으로 튕깁니다.
세션 쿠키가 저장되지 않는 경우(도메인/SESSION_DOMAIN 불일치, HTTPS 전용 쿠키를 HTTP 로 접속) 또는 계정이 is_close=1(비활성)로 바뀐 경우입니다. 관리자에게 계정 상태를 확인합니다.
"입력한 자격 증명이 일치하지 않습니다." 가 뜹니다.
login_id 오타, 비밀번호 불일치, 또는 계정 비활성(is_close=1)입니다. 보안상 어느 쪽인지는 구분해 알려주지 않습니다.
계정 로그인이 안 됩니다. 비밀번호를 잊으셨거나 계정이 비활성화되었을 수 있습니다. 관리자가 Users & Teams에서 비밀번호를 재설정합니다.
로그인은 되는데 /admin 에 못 들어갑니다.
등급이 10 미만입니다. /admin 은 infr.level:10 이므로 관리자만 접근합니다. 마찬가지로 /security·/backup 은 레벨 2 이상이 필요합니다.
AJAX 호출이 401 을 반환합니다.
콘솔 세션이 만료됐습니다. 페이지를 새로고침해 다시 로그인합니다. JSON 요청은 리다이렉트 대신 {"error":"unauthenticated"} 401 을 받습니다.
관리자가 내 등급을 올렸는데 반영이 안 됩니다.
InfrAuth 가 매 요청마다 세션을 재동기화하므로 다음 페이지 이동부터 반영됩니다. 재로그인은 필요 없습니다. 즉시 반영이 필요하면 새로고침합니다.
메뉴/화면 언어가 예상과 다릅니다.
표시 언어는 infr_user.lang(세션 infr_lang)을 따르며 매 요청 재동기화됩니다. 언어 설정에서 변경합니다.
API 가 401 invalid_api_key / 429 rate_limited 를 줍니다.
키가 비활성이거나 오타이면 401, 분당 120회를 넘기면 429(+Retry-After)입니다. API 발급에서 키 상태를 확인하고 요청 빈도를 낮춥니다.
관련 항목
- RBAC 등급 —
user_level↔min_req, demo/staff/admin 티어 - 역할 · 팀 — 역할 스코프와 팀 소유권
- Users & Teams 관리 — 계정 생성·비밀번호 재설정·비활성화
- API 발급 — 인제스트 API 키 발급과 연동 코드
- 언어 설정 — 표시 언어와 로케일 폴백
- 감사 로그 — 파괴적 작업 추적
