당신의 API는 기본적으로 공개돼 있습니다 — 해결해 봅시다
Source: Dev.to
무서운 생각이 있습니다: 여러분이 배포하는 모든 API는 별도로 비공개로 설정하지 않으면 기본적으로 공개됩니다. 대부분의 백엔드 침해는 엘리트 해커 때문이 아닙니다. 지루해진 스크립트, 유출된 토큰, 혹은 존재한다는 것을 잊어버린 엔드포인트에서 발생합니다. 이제 제가 아직도 프로덕션에서 흔히 보는 가장 일반적인 API 보안 실수를 살펴보고, 사용성을 해치지 않으면서 백엔드를 강화하는 방법을 알아보겠습니다.
Authentication vs. Authorization
Authentication 은 당신이 누구인지에 답합니다.
Authorization 은 당신이 무엇을 할 수 있는지에 답합니다.
많은 API가 인증 단계에서 멈춥니다.
GET /api/users/123
Authorization: Bearer <token>
토큰이 유효하면 요청이 성공합니다 — 사용자가 해당 데이터를 볼 권한이 없더라도.
// ❌ Only checks auth
if (!req.user) throw new UnauthorizedError();
// ✅ Checks authorization
if (req.user.id !== params.userId && !req.user.isAdmin) {
throw new ForbiddenError();
}
보안 원칙: 모든 읽기, 쓰기, 삭제 작업은 “왜 이 사용자가 허용되는가?”라는 질문에 답해야 합니다.
JWT 함정
JWT는 훌륭하지만, 잘못 사용된 JWT는 위험합니다.
일반적인 문제점
- 만료(
exp) 없음 - 토큰을
localStorage에 저장 - 사용자가 로그아웃한 뒤에도 토큰을 영원히 허용
- 청중(
aud) 또는 발행자(iss) 검증 없음
const payload = jwt.verify(token, JWT_SECRET, {
audience: 'api.myapp.com',
issuer: 'auth.myapp.com',
});
모범 사례
- 짧은 수명의 액세스 토큰(5–15 분)
httpOnly로 저장된 리프레시 토큰- 리프레시 시 토큰 회전
JWT가 보안을 제공하는 것이 아니라 — 검증 로직이 보안을 제공합니다.
속도 제한
API에 로그인, OTP, 비밀번호 재설정, 검색, 공개 엔드포인트 등이 있다면… 속도 제한이 필요합니다. 필수입니다.
왜 중요한가
- 제한이 없으면 무차별 대입 공격이 쉬워집니다
- 스크래퍼가 대역폭을 소모할 수 있습니다
- 하나의 악성 클라이언트가 시스템을 DOS 공격할 수 있습니다
import rateLimit from 'express-rate-limit';
export const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // requests per minute
});
다음에 대해 서로 다른 제한을 적용하세요:
- 인증 엔드포인트
- 공개 API
- 내부 서비스
보안은 사용자를 차단하는 것이 아니라 남용을 제어하는 것입니다.
최소 데이터 노출
일반적인 실제 사례 침해:
{
"id": 42,
"email": "user@example.com",
"passwordHash": "...",
"isAdmin": false,
"createdAt": "..."
}
passwordHash를 노출하려는 의도는 없었습니다.
안전한 DTO
// ✅ Safe DTO
return {
id: user.id,
email: user.email,
createdAt: user.createdAt,
};
절대 의존하지 마세요:
- ORM 기본 직렬화 (
res.json(entity)) - “프론트엔드에서 필터링하겠습니다”
필드 화이트리스트에 포함시키지 않았다면, 서버를 떠나서는 안 됩니다.
CORS 오해
“안전해, 우리 CORS는 잠겨 있어.”
CORS는 브라우저에만 영향을 미칩니다. 다음을 막지 않습니다:
- 서버‑간 호출
- 봇, 모바일 앱, Postman,
curl
curl https://api.yoursite.com/secret
CORS는 사용자 브라우저를 악의적인 웹사이트가 악용하는 것을 방지하기 위한 것이며, API를 보호하기 위한 것이 아닙니다.
비밀 관리
레포지토리에 다음과 같은 내용이 포함된 적이 있다면:
.env파일- API 키
- Firebase 설정
- AWS 자격 증명
…이것들이 유출된 것으로 간주하십시오.
권장 사항
- 비밀은 오직 환경 변수에만 저장
- 키를 정기적으로 교체
- 비밀을 절대 로그에 남기지 않기 (디버그에서도)
- 키의 권한을 최소한으로 제한
키가 유출되더라도 영향을 받는 범위는 작아야 하며, 치명적인 수준이 되어서는 안 됩니다.
감사 및 로깅
문제가 발생했을 때, 답이 필요합니다:
- 누가 했나요?
- 언제였나요?
- 어디서였나요?
- 어떤 토큰을 사용했나요?
보안 관련 작업을 로그에 남기지 않으면, 상황을 알 수 없습니다.
로그 기록 (하지만 과도하게 기록하지 않기)
- 인증 시도
- 권한 실패
- 관리자 작업
- 토큰 갱신
- 역할 변경
의도적으로 기록하고, 불필요하게 많이 기록하지 마세요.
마이크로서비스와 제로 트러스트
엔드포인트가 다음과 같은 경우라도:
- VPC 뒤에 있음
- 프라이빗 서브넷에 있음
- “서비스에 의해서만 호출됨”
…안전하다는 의미는 아닙니다.
서비스 간 통신 강화
- 서비스 간 인증 (예: mTLS, 서명된 JWT)
- 단명 토큰
- 네트워크 수준 규칙
- 명시적 권한
제로 트러스트는 편집증이 아니라 현실주의입니다.
요약: 지루하고 규율 있는 결정 체크리스트
- 모든 작업에 대한 명시적 권한 부여
- 최소 데이터 노출 (화이트리스트 필드)
- 전역 레이트 제한 (인증, 공개, 내부)
- 단기간 자격 증명 (액세스 및 리프레시 토큰)
- 명확한 감사 로그 (인증, 권한, 관리자 작업)
대부분의 침해 사고는 정교한 악용으로부터 발생하지 않습니다. 변경을 잊은 기본 설정에서 비롯됩니다. 이러한 규율 있는 관행을 구현하면 API의 공격 표면을 크게 줄일 수 있습니다.