지난주에 이어 가벼운 운영 글이에요. 저희 도면 분석 콘솔은 원래 사내용이라 로그인 기능이 없어요. 그런데 사이트에서 "라이브 데모"로 연결하려면 외부에 열어야 하고, 열면 아무나 도면을 올리고 지울 수 있어요. 사용자 모델을 새로 만들기엔 과하고, 그렇다고 그냥 열 수는 없고. HTTP Basic auth로 갔어요.
왜 Basic auth냐면요
- 앱 코드를 거의 안 건드려요. 미들웨어 한 파일이에요.
- 브라우저가 로그인 창을 띄워 줘요. UI를 만들 필요가 없어요.
- HTTPS 위에서는 자격 증명이 암호화돼요. HTTPS 없이는 절대 안 돼요.
- 한계도 분명해요. 사용자별 권한이 없고, 로그아웃이 애매하고, "비밀번호를 아는 사람 = 같은 권한"이에요. 데모 용도로는 받아들일 만했어요.
미들웨어 구조
Next.js 미들웨어에서 Authorization 헤더를 확인하고, 없거나 틀리면 401과 WWW-Authenticate를 돌려줘요. 핵심 결정 몇 가지예요.
1. 설정이 없으면 열려 있어요. 환경 변수에 계정이 하나도 없으면 인증을 안 해요. 로컬 개발과 사내망 배포에서는 비밀번호 없이 쓰려고요. 공개 배포에서만 환경 변수를 넣어요. 단, 이 "기본 열림"은 실수하면 위험하니까 배포 스크립트의 헬스체크가 인증 없이 401이 오는지 확인해요. 200이 오면 배포 실패예요.
2. 계정은 여러 개예요. 관리자 계정과 데모 계정을 분리했어요. 환경 변수 하나에 user:pass,user2:pass2 형식으로 넣고 파싱해요. 데모 비밀번호가 새면 데모 계정만 바꾸면 돼요.
3. 비밀번호는 저장소에 없어요. 서버의 root만 읽을 수 있는 env 파일에 있고, systemd가 서비스에 넘겨요. 코드, 설정 파일, 로그 어디에도 없어요.
상수 시간 비교
비밀번호를 === 로 비교하면 첫 글자가 틀렸을 때와 마지막 글자가 틀렸을 때 응답 시간이 아주 조금 달라요. 이론적으로는 그 차이로 한 글자씩 맞춰 갈 수 있어요. 그래서 길이가 같으면 끝까지 XOR로 비교하는 상수 시간 비교를 써요.
function safeEqual(a, b) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
return diff === 0;
}
계정이 여러 개일 때도 마찬가지예요. 첫 계정이 맞으면 바로 return 하지 않고, 모든 계정을 다 비교한 뒤 결과를 내요. 안 그러면 응답 시간으로 "이 아이디는 존재한다"를 알 수 있어요. 실제 위협이 될 만한 상황은 아니지만, 몇 줄이면 되는 걸 안 할 이유가 없어요.
매처(matcher) 함정
미들웨어가 어느 경로에 적용되는지 정하는 매처에서 한 번 당했어요. 정적 자산을 제외하는 흔한 패턴 /((?!_next/static|...).*) 을 썼는데, 이 패턴이 루트 경로 / 에는 안 걸렸어요. API 경로는 401인데 페이지 자체는 인증 없이 열리는 상태였어요. / 를 매처에 따로 추가해서 고쳤고, 위에서 말한 "인증 없이 401인지" 헬스체크를 루트 경로로 하게 바꿨어요. 이런 건 코드 리뷰로는 못 잡아요. 실제로 curl로 쳐 봐야 해요.
데모 계정 안내 페이지
데모 계정은 사이트의 데모 안내 페이지에 그대로 적혀 있어요. 공개 정보예요. 다만 검색엔진에 색인되는 건 원치 않아서 그 페이지는 noindex 와 robots.txt 차단을 걸었어요. "비밀"은 아니지만 "검색하면 나오는" 것도 아니게요. 완벽한 보호가 아니라 노출 범위 조절이에요.
데모 계정의 한계와 운영
Basic auth라 데모 계정도 관리자와 같은 권한이에요. 도면을 올리고 지울 수 있어요. 지금은 이렇게 운영해요.
- 데모 데이터는 주기적으로 초기화할 수 있게 해 뒀어요.
- 데모 계정으로 쓰기 요청(POST/DELETE)을 막는 건 미들웨어에 몇 줄이면 돼서, 필요해지면 바로 넣을 수 있어요.
- 비밀번호를 바꾸면 서버 env와 사이트 안내 페이지 두 군데를 맞춰야 해요. 이건 사람이 기억해야 하는 일이라 README에 적어 뒀어요.
정리
- 사용자 모델 없는 앱을 잠깐 열 때 Basic auth는 괜찮은 선택이에요. HTTPS가 전제예요.
- 설정 없으면 열림 + 배포 헬스체크로 "닫혀 있는지" 확인.
- 계정을 분리하고, 비밀번호는 저장소 밖에.
- 비교는 상수 시간으로, 계정 전부를 비교한 뒤 판정.
- 매처는 curl로 직접 확인하세요. 루트가 빠질 수 있어요.
다음 주부터는 다시 본업으로 돌아가요. 4분기에는 도면 세트 간 참조 관계(조립도 ↔ 단품도)를 자동으로 연결하는 작업을 하고 있는데, 그 진행 상황을 쓸게요. 읽어 주셔서 고마워요!