← 목록으로

서비스 보안의 기초: 개발자가 꼭 알아야 할 것들

소규모 서비스에서 자주 발생하는 보안 취약점과 최소한의 노력으로 보안을 강화하는 방법을 설명합니다.

보안은 큰 기업만의 문제가 아닙니다. 오히려 소규모 서비스가 더 취약한 경우가 많습니다. 보안 전담 인력이 없고, 빠른 개발에 집중하다 보면 기본적인 보안 설정을 놓치기 쉽습니다. 이 글에서는 개발자가 꼭 알아야 할 기본 보안 사항을 설명합니다.

가장 흔한 보안 취약점 (OWASP Top 10)

OWASP(Open Web Application Security Project)는 매년 웹 애플리케이션의 주요 보안 위협을 발표합니다.

1. SQL 인젝션

사용자 입력을 그대로 SQL 쿼리에 삽입하면 발생합니다.

취약한 코드:

// 절대 이렇게 하면 안 됩니다
const query = `SELECT * FROM users WHERE email = '${userInput}'`;

사용자가 ' OR '1'='1를 입력하면 모든 사용자 데이터가 노출됩니다.

안전한 코드:

// 파라미터화된 쿼리 사용
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [userInput]);

Prisma, Drizzle 같은 ORM을 사용하면 SQL 인젝션을 기본적으로 방지합니다.

2. XSS (크로스 사이트 스크립팅)

사용자 입력을 HTML에 그대로 삽입하면 악성 스크립트가 실행됩니다.

React를 사용하면 대부분의 XSS는 자동으로 방지됩니다. 하지만 dangerouslySetInnerHTML을 사용하는 경우 반드시 입력값을 sanitize해야 합니다.

// DOMPurify로 HTML sanitize
import DOMPurify from 'dompurify';
const cleanHTML = DOMPurify.sanitize(userInput);

3. 인증/인가 취약점

  • 약한 비밀번호: 최소 8자, 복잡도 요구 또는 패스프레이즈 권장
  • 무제한 로그인 시도: 로그인 실패 5회 후 잠금 또는 CAPTCHA 적용
  • JWT 잘못된 검증: 토큰의 서명과 만료 시간을 반드시 검증

환경 변수와 시크릿 관리

API 키, 데이터베이스 비밀번호를 코드에 직접 넣는 실수를 많은 개발자가 합니다.

절대 하면 안 되는 것:

// 코드에 직접 시크릿 넣기 금지!
const apiKey = 'sk-1234567890abcdef';
const dbPassword = 'mypassword';

올바른 방법:

// 환경 변수로 관리
const apiKey = process.env.OPENAI_API_KEY;
const dbPassword = process.env.DATABASE_PASSWORD;

.env 파일은 반드시 .gitignore에 추가하세요. GitHub에 시크릿이 올라가면 수분 내에 자동화된 봇이 발견해서 악용합니다.

프로덕션 시크릿은 Vercel, Railway의 환경 변수 설정을 사용하거나, AWS Secrets Manager, HashiCorp Vault를 사용합니다.

HTTPS 강제 적용

모든 트래픽을 HTTPS로 처리합니다. HTTP로 접근 시 자동으로 HTTPS로 리다이렉트합니다.

Vercel, Cloudflare는 자동으로 HTTPS를 적용하고 인증서를 관리합니다. 직접 서버를 운영한다면 Let's Encrypt로 무료 SSL 인증서를 발급합니다.

보안 헤더 설정

HTTP 응답 헤더에 보안 설정을 추가합니다.

Next.js 보안 헤더 설정:

// next.config.mjs
const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'X-Frame-Options', value: 'SAMEORIGIN' },  // 클릭재킹 방지
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-eval'"
  },
];

export default {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }];
  },
};

의존성 취약점 관리

사용하는 npm 패키지에 알려진 취약점이 있을 수 있습니다.

# 취약점 확인
npm audit

# 자동 수정
npm audit fix

GitHub의 Dependabot을 활성화하면 취약한 의존성이 발견될 때 자동으로 PR을 만들어줍니다.

비밀번호 저장

절대로 비밀번호를 평문으로 저장하지 마세요. bcrypt 또는 Argon2로 해시합니다.

import bcrypt from 'bcrypt';

// 저장 시
const hashedPassword = await bcrypt.hash(plainPassword, 12);

// 검증 시
const isValid = await bcrypt.compare(plainPassword, hashedPassword);

NextAuth.js, Supabase Auth, Clerk 같은 인증 라이브러리를 사용하면 이런 처리를 대신 해줍니다.

Rate Limiting

동일 IP에서 과도한 요청을 제한합니다. 브루트 포스 공격, API 남용을 방지합니다.

// Next.js에서 rate limiting 예시 (upstash/ratelimit 사용)
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'), // 10초에 10회
});

보안 체크리스트

배포 전 확인 사항:

  • 모든 시크릿이 환경 변수로 관리됨
  • .env 파일이 .gitignore에 있음
  • HTTPS 강제 리다이렉트 설정
  • SQL 쿼리에 파라미터화 적용
  • 사용자 입력 sanitize 처리
  • npm audit 취약점 없음
  • 비밀번호 bcrypt 해시 저장
  • Rate limiting 적용

보안은 완벽할 수 없지만 기본적인 것만 잘 지켜도 대부분의 공격을 막을 수 있습니다. 완벽한 보안보다 기본을 빠짐없이 챙기는 것이 먼저입니다.