문서

문서

ActiCrawl을 사용하여 웹 스크래핑 워크플로를 자동화하는 방법을 알아보세요

보안 고려사항

ActiCrawl을 사용한 웹 스크래핑 시 데이터를 보호하고 안전한 운영을 유지하기 위해 적절한 보안 조치를 이해하고 구현하는 것이 중요합니다. 이 가이드는 필수 보안 고려사항과 모범 사례를 다룹니다.

API 키 보안

API 키 보호하기

API 키는 ActiCrawl 서비스에 접근하기 위한 주요 인증 방법입니다. 비밀번호처럼 취급하세요:

  • 버전 관리 시스템에 API 키를 커밋하지 마세요 (git, SVN 등)
  • 환경 변수를 사용하여 민감한 자격 증명을 저장하세요
  • 정기적으로 키를 교체하세요
  • 키 권한을 최소 필요 접근으로 제한하세요
bash
# 좋은 예: 환경 변수 사용
export ACTICRAWL_API_KEY="your-api-key-here"
curl -H "Authorization: Bearer $ACTICRAWL_API_KEY" ...

# 나쁜 예: 키 하드코딩
curl -H "Authorization: Bearer sk_live_abc123..." ...

환경별 키 사용

각 환경에 다른 API 키를 사용하세요:

javascript
// config.js
const config = {
  development: {
    apiKey: process.env.ACTICRAWL_DEV_KEY
  },
  production: {
    apiKey: process.env.ACTICRAWL_PROD_KEY
  }
};

데이터 보호

민감한 데이터 처리

민감한 정보가 포함된 웹사이트를 스크래핑할 때:

  1. 데이터 수집 최소화 - 필요한 것만 수집하세요
  2. 전송 중 데이터 암호화 - 모든 ActiCrawl API 호출은 HTTPS 사용
  3. 안전한 데이터 저장 - 저장된 민감한 데이터 암호화
  4. 접근 제어 구현 - 스크래핑된 데이터에 접근할 수 있는 사람 제한

GDPR 및 개인정보 보호 준수

javascript
// 예시: 개인 데이터 익명화
function anonymizeData(scrapedData) {
  return {
    ...scrapedData,
    email: hashEmail(scrapedData.email),
    phone: null, // 전화번호 제거
    name: scrapedData.name.substring(0, 1) + '***'
  };
}

네트워크 보안

IP 화이트리스팅

프로덕션 환경에서는 IP 화이트리스팅 구현을 고려하세요:

javascript
// IP 제한을 위한 미들웨어 예시
const allowedIPs = ['203.0.113.0', '203.0.113.1'];

function ipWhitelist(req, res, next) {
  const clientIP = req.ip;
  if (allowedIPs.includes(clientIP)) {
    next();
  } else {
    res.status(403).json({ error: '접근 거부' });
  }
}

속도 제한

남용을 방지하기 위해 속도 제한을 구현하세요:

javascript
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15분
  max: 100, // 각 IP당 windowMs당 100개 요청으로 제한
  message: '이 IP에서 너무 많은 요청이 발생했습니다'
});

app.use('/api/', limiter);

인증 및 권한 부여

안전한 토큰 저장

다음 위치에 인증 토큰을 저장하지 마세요:
- 로컬 스토리지 (XSS 취약)
- 세션 스토리지 (XSS 취약)
- HttpOnly 플래그 없는 쿠키

javascript
// 안전한 쿠키 예시
res.cookie('auth_token', token, {
  httpOnly: true,
  secure: true, // HTTPS 전용
  sameSite: 'strict',
  maxAge: 3600000 // 1시간
});

토큰 검증

항상 서버 측에서 토큰을 검증하세요:

javascript
async function validateRequest(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: '토큰이 제공되지 않았습니다' });
  }

  try {
    const isValid = await verifyToken(token);
    if (isValid) {
      next();
    } else {
      res.status(401).json({ error: '유효하지 않은 토큰' });
    }
  } catch (error) {
    res.status(500).json({ error: '토큰 검증 실패' });
  }
}

입력 검증

URL 정제

스크래핑 전 항상 URL을 검증하고 정제하세요:

javascript
const { URL } = require('url');

function validateUrl(urlString) {
  try {
    const url = new URL(urlString);

    // HTTP(S) 프로토콜만 허용
    if (!['http:', 'https:'].includes(url.protocol)) {
      throw new Error('유효하지 않은 프로토콜');
    }

    // SSRF 공격 방지
    const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0'];
    if (blockedHosts.includes(url.hostname)) {
      throw new Error('차단된 호스트');
    }

    return url.toString();
  } catch (error) {
    throw new Error('유효하지 않은 URL');
  }
}

요청 검증

모든 수신 요청을 검증하세요:

javascript
const Joi = require('joi');

const scrapeSchema = Joi.object({
  url: Joi.string().uri().required(),
  format: Joi.string().valid('markdown', 'json', 'html').required(),
  waitFor: Joi.number().min(0).max(30000).optional(),
  screenshot: Joi.boolean().optional()
});

function validateScrapeRequest(req, res, next) {
  const { error } = scrapeSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ 
      error: error.details[0].message 
    });
  }
  next();
}

오류 처리

안전한 오류 메시지

오류 메시지에 민감한 정보를 노출하지 마세요:

javascript
// 나쁜 예: 내부 정보 노출
catch (error) {
  res.status(500).json({ 
    error: error.stack,
    database: error.sqlMessage
  });
}

// 좋은 예: 일반적인 오류 메시지
catch (error) {
  console.error('스크래핑 오류:', error); // 내부적으로 로그
  res.status(500).json({ 
    error: '요청 처리 중 오류가 발생했습니다',
    reference: generateErrorId()
  });
}

모니터링 및 로깅

보안 이벤트 로깅

보안 관련 이벤트를 로그하세요:

javascript
const winston = require('winston');

const securityLogger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ 
      filename: 'security.log',
      maxsize: 5242880, // 5MB
      maxFiles: 5
    })
  ]
});

// 보안 이벤트 로그
securityLogger.info('API_KEY_USED', {
  timestamp: new Date(),
  apiKey: maskApiKey(apiKey),
  ip: req.ip,
  userAgent: req.headers['user-agent']
});

이상 탐지

의심스러운 패턴을 모니터링하세요:

javascript
async function detectAnomalies(userId) {
  const recentRequests = await getRecentRequests(userId, '1h');

  if (recentRequests.length > 1000) {
    await flagAccount(userId, 'HIGH_VOLUME');
  }

  const uniqueIPs = [...new Set(recentRequests.map(r => r.ip))];
  if (uniqueIPs.length > 10) {
    await flagAccount(userId, 'MULTIPLE_IPS');
  }
}

모범 사례 체크리스트

개발

  • [ ] 민감한 데이터에 환경 변수 사용
  • [ ] 모든 통신에 HTTPS 활성화
  • [ ] 적절한 오류 처리 구현
  • [ ] 모든 입력 검증 및 정제
  • [ ] 안전한 코딩 관행 사용

프로덕션

  • [ ] 정기적인 보안 감사
  • [ ] 속도 제한 구현
  • [ ] 의심스러운 활동 모니터링
  • [ ] 종속성 업데이트 유지
  • [ ] 사고 대응 계획 수립

규정 준수

  • [ ] robots.txt 규칙 준수
  • [ ] 웹사이트 이용 약관 존중
  • [ ] 데이터 보존 정책 구현
  • [ ] GDPR 준수 보장
  • [ ] 보안 조치 문서화

보안 헤더

응답에 항상 보안 헤더를 포함하세요:

javascript
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Strict-Transport-Security', 
    'max-age=31536000; includeSubDomains');
  res.setHeader('Content-Security-Policy', 
    "default-src 'self'");
  next();
});

결론

보안은 일회성 구현이 아닌 지속적인 프로세스입니다. 최신 보안 모범 사례를 계속 업데이트하고, 정기적으로 구현을 감사하며, 항상 사용자 데이터 보호를 우선시하세요.

보안 우려사항이나 취약점을 보고하려면 security@acticrawl.com으로 보안팀에 문의해 주세요.