트러블슈팅

사내망 HTTP 배포에서 Clipboard API와 randomUUID가 사라지는 이유

개개발 노하우 노트2026년 11월 11일 · 3분 읽기

개발 PC의 localhost에서는 잘 되던 복사 버튼과 ID 생성이, 사내망 HTTP 주소로 배포하자마자 동작하지 않은 일을 정리했다. 사내 서버에 HTTP로 웹앱을 띄워 쓰는 사람, 특히 로컬 검증만으로 배포를 끝내는 사람에게 필요한 글이다.

상황

사내 개발 서버에 개인용 블로그 편집기를 Next.js로 올려 두고, 사내망 IP 주소에 HTTP로 접속해 쓰고 있었다. 편집기에는 두 가지 브라우저 기능이 들어 있었다.

  • 글 구성 섹션을 추가할 때 `crypto.randomUUID()`로 섹션 ID를 만든다.
  • 업로드한 파일 주소를 Clipboard API(`navigator.clipboard.writeText`)로 복사한다.

개발 PC에서 `localhost`로 띄워 확인할 때는 둘 다 문제없었다.

증상

배포한 서버에 사내망 주소로 접속하자 섹션 추가가 오류로 멈추고 파일 주소 복사 버튼도 아무 반응이 없었다. 브라우저 콘솔에서 직접 확인해 보니 다음과 같았다.

  • `isSecureContext`가 `false`
  • `navigator.clipboard`가 없음
  • `typeof crypto.randomUUID`가 `undefined`

코드와 빌드는 로컬과 같았는데 브라우저가 내주는 API 자체가 달랐다.

원인

두 API는 모두 보안 컨텍스트(secure context)에서만 쓸 수 있다. MDN 문서는 Clipboard API와 `Crypto.randomUUID()`를 보안 컨텍스트 전용 기능으로 표시한다.

핵심은 무엇이 보안 컨텍스트인지다. HTTPS로 받은 문서 외에도 `localhost`, `127.0.0.1` 같은 루프백 주소는 HTTP여도 "잠재적으로 신뢰할 수 있는" 출처로 취급된다. 반면 `192.168.x.x` 같은 사내망 IP에 HTTP로 접속한 문서는 보안 컨텍스트가 아니다.

그래서 로컬 검증은 통과하고 배포 환경에서만 깨졌다. 로컬과 배포의 차이는 코드가 아니라 접속한 출처였다.

해결

먼저 각 기능에 보안용 기능이 정말 필요한지 따졌다.

  • 섹션 ID: 화면 안에서 섹션을 구분하는 용도라 예측 불가능한 난수가 필요 없었다. 편집기 인스턴스마다 `useRef`로 증가하는 순번을 두고 ID를 만들도록 바꿨다.
  • 파일 주소 복사: 보안 컨텍스트가 아닐 때는 화면의 입력란에 주소를 넣고 선택해 복사하는 방식으로 대체했다.

```ts

const nextId = useRef(0);

const createSectionId = () => `section-${++nextId.current}`;

```

장기적으로는 사내망 서비스도 HTTPS로 여는 편이 맞다. 사설 인증서나 사내용 리버스 프록시를 두면 이런 차이 자체가 사라진다.

운영자 해설

`randomUUID`를 쓰지 못한다고 `Math.random()` 기반 UUID 함수를 끼워 넣는 해결은 주의해야 한다. 그 값이 세션 토큰이나 추측되면 안 되는 키로 쓰인다면 보안 수준을 조용히 낮추는 셈이다. 용도가 단순 구분인지, 보안이 필요한지부터 나누는 것이 먼저다.

또 하나는 검증 기준이다. localhost는 브라우저 입장에서 특별 대우를 받는 주소라서, 거기서의 성공은 배포 환경에서의 성공을 보장하지 않는다. 특히 Service Worker, 위치 정보, 카메라처럼 보안 컨텍스트를 요구하는 기능이 더 있다면 같은 문제가 반복된다.

다시 겪지 않으려면

  • 브라우저 API를 쓰기 전에 MDN에서 보안 컨텍스트 전용 여부를 확인한다.
  • 사내망 HTTP로 배포한다면 `window.isSecureContext`를 확인하고 대체 경로를 둔다.
  • 보안용 난수가 필요 없는 ID는 순번이나 서버 발급 값으로 만든다.
  • 보안이 필요한 값은 대체 구현을 넣지 말고 HTTPS로 서비스를 연다.
  • 배포 검증은 실제 사용자가 접속하는 주소에서 한다. localhost 성공만으로 판단하지 않는다.

참고 자료