보안 고려사항
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
}
};
데이터 보호
민감한 데이터 처리
민감한 정보가 포함된 웹사이트를 스크래핑할 때:
- 데이터 수집 최소화 - 필요한 것만 수집하세요
- 전송 중 데이터 암호화 - 모든 ActiCrawl API 호출은 HTTPS 사용
- 안전한 데이터 저장 - 저장된 민감한 데이터 암호화
- 접근 제어 구현 - 스크래핑된 데이터에 접근할 수 있는 사람 제한
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으로 보안팀에 문의해 주세요.