인증 및 보안
Libre WebUI는 JWT 세션을 사용하는 로컬 사용자 계정을 지원합니다. 새 설치에서는 항상 로컬 관리자 한 명을 처음 만들 수 있습니다. 이후 모든 로컬 또는 OAuth 계정의 공개 등록은 기본적으로 닫혀 있습니다.
최초 설정
데이터베이스에 사용자가 없으면:
- Libre WebUI가 최초 설정 흐름을 표시합니다.
- 사용자가 첫 로컬 계정을 만듭니다.
- 계정에
admin역할이 할당됩니다. - 명시적으로 활성화하지 않는 한 이후 공개 등록은 계속 닫혀 있습니다.
기존 데이터베이스의 사용자와 역할은 그대로 유지됩니다.
로컬 계정
로컬 가입에는 다음이 필요합니다.
- 사용자 이름
- 대문자, 소문자, 숫자를 포함한 12자~72 UTF-8 바이트의 비밀번호
- 선택적 이메일
비밀번호는 저장 전에 bcrypt로 해시됩니다. 로그인 및 가입 경로에는 속도 제한이 있습니다.
등록 승인
공개 등록만으로 접근 권한이 생기지는 않습니다. 공개 가입 양식 또는 OAuth 제공자로 만든 모든 계정은 pending 상태로 시작하며 로그인 전에 관리자의 승인이 필요합니다.
유일한 예외는 초기 설정입니다. 빈 데이터베이스의 첫 실제 계정은 admin 역할과 active 상태로 원자적으로 생성되어 새 설치에서도 관리자가 정상 작동합니다. 이후 모든 등록은 검토를 기다립니다.
보류 중인 사용자에게 보이는 내용:
- 가입은 성공하지만 세션 토큰을 반환하지 않습니다. API는
approvalRequired: true와 함께202를 반환하고 UI는 관리자의 승인이 필요하다고 설명합니다. - 올바른 비밀번호로 로그인해도
403과 코드ACCOUNT_PENDING("계정이 관리자 승인을 기다리고 있습니다")으로 거부됩니다. OAuth 로그인은?approval=pending과 함께 로그인 페이지로 돌아갑니다. - 인증된 요청마다 데이터베이스에서 계정 상태를 다시 읽으므로 세션이 계정의
active상태보다 오래 지속될 수 없습니다.
관리자에게 보이는 내용:
- 사용자 관리의 승인 대기 카드에 대기 계정이 표시되며 각 항목에 계정 활성화와 거부 작업이 있습니다. 거부는 삭제이며 별도 정지 상태는 없습니다.
- 관리자가 로그인한 동안 앱에서 알림을 받습니다. 새 등록이 오면 사용자 항목에 배지와 토스트가 나타납니다. 승인 대기 요약은 약 1분마다 폴링합니다(
GET /api/users/pending-approvals, 관리자 전용). - 승인(
PATCH /api/users/:id/approve, 관리자 전용)은 승인한 관리자와 시각을 기록합니다. 역할은 바꾸지 않으므로 승인 계정은 관리자가 승격할 때까지user역할을 유지합니다. 다음 로그인 시도부터 적용되며 다시 만들 필요가 없습니다.
업그레이드는 기존 계정에 영향을 주지 않습니다. 기능 출시 후 공개 등록으로 만든 계정만 보류 상태로 시작합니다. 관리자가 사용자 관리에서 만든 계정은 즉시 활성화됩니다.
의도적으로 공개 등록 활성화
등록은 기본적으로 비활성화됩니다. 새 로컬 또는 OAuth 계정을 받아야 하는 기간에만 다음 백엔드 환경 변수를 설정하세요.
ENABLE_SIGNUP=true
계획한 등록 기간이 끝나면 false로 되돌리세요. 공개 등록이 닫혀 있어도 기존 로컬·OAuth 사용자는 로그인할 수 있고, 관리자는 사용자 관리에서 계정을 만들 수 있습니다.
ENABLE_SIGNUP=false이어도 빈 데이터베이스는 로컬 관리자 한 명을 항상 허용합니다. OAuth는 이 초기 슬롯을 차지할 수 없습니다. 비공개 원격 배포에서는 애플리케이션 시작 전에 Cloudflare Access 같은 신원 허용 목록 뒤에 호스트 이름을 두고, 보호된 경로에서 첫 관리자를 만드세요.
역할
| 역할 | 용도 |
|---|---|
admin | 인스턴스 관리, 사용자 관리, 시스템 설정, 신뢰된 Work 런타임 운영 |
user | 일반 채팅, 모델, 페르소나, 문서, 설정 워크플로 |
모델 설치, 삭제, 복사, 푸시, 언로드는 호스트 리소스를 변경하므로 관리자에게만 허용됩니다.
Work 접근
Work는 선택한 모델이 관리 컨테이너 안에서 임의 명령을 실행할 수 있으므로 기본적으로 관리자 전용입니다. 관리자는 설정의 사용자 관리 탭에서 모든 활성 사용자에게 Work를 열 수 있습니다. 설정은 재시작 후에도 유지되고 열린 터미널 세션에도 즉시 적용됩니다. 호스트 폴더 워크스페이스는 서버 경로를 바인드 마운트하므로 모든 모드에서 관리자 전용입니다. Work 접근을 받은 모든 사람을 단순 WebUI 사용자가 아니라 신뢰할 수 있는 런타임 운영자로 취급하세요.
관리자 권한은 기존 JWT에 캐시된 역할만이 아니라 현재 데이터베이스 역할을 기준으로 확인합니다. 따라서 관리자를 강등하면 Work 접근이 즉시 취소됩니다. 백엔드는 작업 레코드와 이름 있는 볼륨을 보존하면서 활성 실행을 중단하고 사용자의 Work 컨테이너와 미리보기를 중지하려고 합니다. Docker 정리에 실패해도 접근은 취소된 상태로 유지되며 역할 변경이 실패를 보고합니다. 운영자는 Docker 접근을 복구하고 정리를 다시 시도해야 합니다.
사용자 삭제는 해당 사용자의 Work 데이터를 파괴합니다. Libre WebUI는 관리 대상 컨테이너를 중지하고 Work 볼륨을 제거한 후 계정과 데이터베이스 레코드를 삭제합니다. Docker가 정리 성공을 증명하지 못하면 계정 삭제가 실패해 관리자가 런타임 문제를 해결하고 다시 시도할 수 있습니다.
그룹 및 리소스 권한
관리자는 설정의 사용자 관리 탭에서 그룹을 만들고 구성원을 관리할 수 있습니다. 그룹은 리소스 권한의 주체입니다. 채팅, 노트, 문서, 지식 컬렉션, 폴더, 페르소나, 프롬프트, 스킬 또는 캘린더의 소유자는 접근 API를 통해 사용자나 그룹에 read, write, admin 접근을 부여할 수 있습니다. 모든 공유 가능 화면은 같은 공유 대화 상자를 사용합니다(공유 참조). 관리자도 같은 방식으로 등록된 도구 서버의 범위를 사용자 또는 그룹으로 제한할 수 있습니다. 리소스는 기본적으로 비공개이며 전역 admin 역할은 다른 사용자 콘텐츠에 대한 접근 권한을 주지 않습니다. 구성원 자격은 요청 시 평가되어 구성원을 제거하면 그룹 권한이 즉시 취소됩니다. 설정의 사용자 관리 탭에 있는 "유효 접근" 보기는 역할, 그룹, 기능 접근, 도달하는 모든 권한을 나열해 "이 사용자는 왜 접근할 수 있나요?"에 답합니다.
보안 감사 로그
로그인과 실패, 로그아웃, 세션 및 토큰 취소, 사용자·그룹·권한·토큰 변경 같은 보안 민감 작업은 사용량 분석과 분리된 추가 전용 감사 로그에 기록됩니다. 저장 전에 세부 정보에서 민감 정보가 제거됩니다. 시크릿처럼 보이는 키는 삭제되고 페이로드 크기가 제한되어 비밀번호, 토큰, 프롬프트 내용이 로그에 들어가지 않습니다. 그룹과 권한 변경은 같은 데이터베이스 트랜잭션 안에 감사 이벤트를 작성하므로 기록 없이 변경이 존재할 수 없습니다. 관리자는 설정의 사용자 관리 탭에서 로그를 조회할 수 있고 기본 보존 기간은 180일입니다(AUDIT_RETENTION_DAYS).
세션
백엔드는 JWT_SECRET으로 JWT에 서명합니다. 프로덕션에서는 안정된 시크릿을 설정하세요.
JWT_SECRET=replace-with-a-long-random-secret
JWT_SECRET을 바꾸면 기존 세션이 무효화됩니다. 로컬 및 OAuth 로그인 토큰은 기본값이 7d인 JWT_EXPIRES_IN을 사용하며, 값을 바꾸면 새 세션에 적용됩니다. WebSocket 연결은 영구 토큰을 짧게 유효한 1회용 티켓으로 교환하고 기반 세션이 만료되면 닫힙니다.
로그인할 때마다 JWT에 연결되는 서버 측 세션 레코드도 생성됩니다. 설정 → 세션에는 각 장치의 로그인 방식, 첫 활동과 최근 활동, 만료가 표시됩니다. 여기서 세션을 취소하거나 "다른 세션에서 로그아웃"하면 모든 복제본에서 토큰이 즉시 무효화되고 실시간 WebSocket 연결도 닫힙니다. 로그아웃도 같은 방식으로 현재 세션을 취소합니다. 이 기능 전에 발급된 토큰에는 세션 ID가 없어 만료까지 유효하지만, 새 로그인에서 "다른 세션에서 로그아웃"을 사용하면 계정별 컷오프를 기록해 해당 토큰도 거부합니다.
2단계 인증 및 패스키
설정 → 세션에서 2단계 인증과 비밀번호 없는 로그인을 모두 관리합니다.
- 인증 앱(TOTP). 등록 시 모든 인증 앱용 base32 시크릿과
otpauth://링크를 표시합니다. 첫 6자리 코드를 확인하면 활성화되고 1회용 복구 코드 10개가 표시됩니다. 이후 비밀번호 로그인은 세션 대신 짧게 유효한 챌린지를 반환하며POST /api/auth/mfa/verify가 TOTP 코드 또는 복구 코드로 로그인을 완료합니다. 수락된 코드의 시간 단계를 기록해 가로챈 코드를 재사용할 수 없습니다. 복구 코드는 키 기반 단방향 조회 토큰으로만 저장되고 정확히 한 번씩 작동합니다. 비활성화 또는 복구 코드 재생성에는 요소를 다시 증명해야 합니다. - 패스키(WebAuthn). "패스키로 로그인"은 검색 가능한 자격 증명으로 비밀번호 없이 로그인합니다. 등록 및 로그인에서 사용자 확인(화면 잠금, 생체 인식 또는 PIN)이 필요합니다. Attestation은
none으로 받고 ES256 및 EdDSA 자격 증명을 지원합니다. 자격 증명 자료는 저장 시 암호화되고 ID는 키 기반 조회 토큰으로 보관됩니다. 챌린지는 1회용이며 5분 후 만료됩니다. 0이 아닌 서명 카운터가 증가하지 않으면 복제 신호로 거부합니다. 패스키에는 안전한 HTTPS 출처 또는 개발 환경의localhost가 필요합니다. 인스턴스를 둘 이상의 호스트 이름으로 사용하면WEBAUTHN_RP_ID를 설정하세요.
올바른 비밀번호 뒤에 발급되는 MFA 챌린지 토큰은 JWT_SECRET에서 파생되지만 서로 다른 시크릿으로 서명됩니다. API 요청을 인증할 수 없고 계정 하나와 목적 하나에 묶이며 성공 시 소비됩니다.
관리자는 모든 계정에 2단계 인증을 요구할 수 있습니다(사용자 → 2단계 정책 카드 또는 MFA_REQUIRED_MODE=required로 고정). 설정하지 않은 사용자는 다음 로그인에서 세션 발급 전에 등록 과정을 거칩니다. 계정 복구를 위해 관리자도 사용자 목록에서 TOTP 등록을 초기화할 수 있습니다. 패스키는 사용자가 설정에서 직접 관리하므로 그대로 둡니다. 등록, 활성화, 확인 실패, 비활성화, 정책 변경, 패스키 등록/제거, 관리자 초기화는 모두 보안 감사 로그에 기록됩니다.
MFA는 비밀번호 로그인에 적용됩니다. OAuth 및 OIDC 로그인은 신원 제공자의 2단계 인증을 신뢰하며 다시 묻지 않습니다. API 토큰은 세션 인증을 거치지 않으므로 영향을 받지 않습니다.
API 토큰
설정 → API 키에서 프로그램 사용용 개인 접근 토큰(접두사 lwk_)을 발급합니다. 시크릿은 한 번만 표시되고 해시로만 저장됩니다. 각 토큰은 명시적 범위 목록(chat, models, documents, notes, personas, media, work, admin)을 갖습니다. 백엔드는 각 경로 계열을 필요한 범위에 매핑하므로 노트 전용 토큰으로 채팅이나 관리에 접근할 수 없고, 토큰으로 세션 관리에 접근할 수 없습니다. 토큰은 선택적 만료를 지원하고 최근 사용을 추적하며 언제든 취소할 수 있고 복제본 전체에서 토큰별 속도 제한이 적용됩니다. 관리자 범위 토큰은 관리자만 발급할 수 있으며 사용할 때도 계정에 관리자 역할이 있어야 합니다. chat 범위 토큰은 OpenAI 호환 공개 /v1 API의 키이기도 합니다.
Cloudflare Turnstile
두 키가 모두 설정되면 Turnstile이 비밀번호 로그인과 가입을 보호합니다.
TURNSTILE_SITE_KEY=...
TURNSTILE_SECRET_KEY=...
TURNSTILE_EXPECTED_HOSTNAME=chat.example.com
프런트엔드는 서로 다른 login 및 signup 작업을 할당합니다. 백엔드는 Cloudflare에서 토큰을 확인하고 호스트 이름이나 작업이 요청과 일치하지 않으면 거부합니다. TURNSTILE_EXPECTED_HOSTNAME을 명시적으로 설정하지 않으면 BASE_URL에서 예상 호스트 이름을 가져옵니다.
키 중 하나라도 없으면 Turnstile이 비활성화됩니다.
GitHub OAuth
설정:
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_CALLBACK_URL=https://your-domain.example/api/auth/oauth/github/callback
GitHub OAuth 흐름은 gh_ 접두사 사용자 이름으로 로컬 사용자를 만들고 기본 user 역할을 부여합니다.
Hugging Face OAuth
설정:
HUGGINGFACE_CLIENT_ID=...
HUGGINGFACE_CLIENT_SECRET=...
HUGGINGFACE_CALLBACK_URL=https://your-domain.example/api/auth/oauth/huggingface/callback
Hugging Face OAuth 흐름은 hf_ 접두사 사용자 이름으로 로컬 사용자를 만들고 기본 user 역할을 부여합니다.
두 OAuth 제공자는 짧게 유효한 HttpOnly, SameSite 쿠키에 연결된 암호학적으로 무작위인 state 값을 사용합니다. 콜백은 누락되거나 일치하지 않는 state를 거부합니다. 콜백에 성공하면 JWT가 60초 HttpOnly 쿠키로 프런트엔드에 전달된 뒤 즉시 교환 및 삭제됩니다. Bearer 토큰은 콜백 URL, 브라우저 기록, referrer 헤더에 절대 들어가지 않습니다.
리디렉션 및 CORS
콜백 기본값에는 BASE_URL, 브라우저 접근에는 CORS_ORIGIN을 설정합니다.
BASE_URL=https://your-domain.example
CORS_ORIGIN=https://your-domain.example
로컬 개발에서는 Vite 개발 출처를 포함합니다.
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
데모 모드
데모 모드는 프런트엔드 미리보기 모드입니다. 비활성화된 데모 자격 증명을 미리 채우고 모의 API 응답을 사용합니다. 프로덕션 인증 모드가 아닙니다.
보안 체크리스트
- 강력한
JWT_SECRET을 설정하세요. DATA_DIR를 접근 제어된 영구 스토리지에 두세요.- 데이터베이스와 함께
ENCRYPTION_KEY를 백업하세요. - 공개 가입에는 Turnstile을 설정하세요.
- 공개 배포에서는 HTTPS를 사용하세요.
- 제공자 API 키를 필요한 최소 범위로 제한하세요.
- OAuth 콜백 URL을 정확히 유지하세요.
- Work 접근(관리자 계정 또는 모든 사용자에게 여는 모드)은 백엔드 컨테이너 런타임을 운영하도록 신뢰할 수 있는 사람에게만 부여하세요.