Tutorial
타임스탬프 검증 모범 사례: 전체 가이드
소개
타임스탬프의 유효성을 검사하는 것은 데이터 무결성, 보안 및 애플리케이션 안정성에 매우 중요합니다. 잘못된 타임스탬프는 데이터베이스 오류, 잘못된 계산, 보안 취약성 및 열악한 사용자 경험으로 이어질 수 있습니다. 이 포괄적인 가이드는 다양한 시나리오에서 타임스탬프 유효성 검사에 대한 모범 사례를 알려줍니다.
타임스탬프를 검증하는 이유는 무엇입니까?
잘못된 타임스탬프와 관련된 일반적인 문제
- 데이터 손상: 잘못된 타임스탬프는 기록 데이터 및 분석을 손상시킬 수 있습니다.
- 보안 위험: 타임스탬프 조작으로 인증 및 승인을 우회할 수 있음
- 비즈니스 로직 오류: 잘못된 타임스탬프는 청구, 예약 및 보고에 영향을 미칠 수 있습니다.
- 통합 실패: 잘못된 형식으로 인해 API 통합 및 데이터 파이프라인이 중단될 수 있습니다.
- 성능 문제: 유효성 검사가 제대로 이루어지지 않으면 데이터베이스 쿼리 비효율성이 발생할 수 있습니다.
실제 영향
// Without validation - vulnerable to attacks
const expiryTime = req.body.expiryTime; // Could be: 9999999999999
if (Date.now() < expiryTime) {
grantAccess(); // Attacker gets permanent access!
}
// With validation - secure
const expiryTime = validateTimestamp(req.body.expiryTime, {
min: Date.now(),
max: Date.now() + 86400000 // 24 hours max
});
if (expiryTime && Date.now() < expiryTime) {
grantAccess(); // Safe access control
}
핵심 검증 기술
1. 범위 검증
타임스탬프가 허용 가능한 범위 내에 있는지 항상 확인하세요.
자바스크립트 예
function validateTimestampRange(timestamp, options = {}) {
const {
min = 0, // Unix epoch start (1970-01-01)
max = 253402300799000, // Year 9999 in milliseconds
precision = 'milliseconds'
} = options;
// Normalize to milliseconds
let ts = timestamp;
if (precision === 'seconds' && String(timestamp).length === 10) {
ts = timestamp * 1000;
} else if (precision === 'microseconds' && String(timestamp).length === 16) {
ts = Math.floor(timestamp / 1000);
}
// Type check
if (typeof ts !== 'number' || isNaN(ts)) {
return { valid: false, error: 'Timestamp must be a valid number' };
}
// Finite check
if (!Number.isFinite(ts)) {
return { valid: false, error: 'Timestamp must be finite' };
}
// Range check
if (ts < min) {
return { valid: false, error: `Timestamp ${ts} is before minimum ${min}` };
}
if (ts > max) {
return { valid: false, error: `Timestamp ${ts} exceeds maximum ${max}` };
}
return { valid: true, normalized: ts };
}
// Usage
const result = validateTimestampRange(1704067200000, {
min: Date.parse('2024-01-01'),
max: Date.parse('2024-12-31')
});
console.log(result);
// { valid: true, normalized: 1704067200000 }
파이썬 예제
from datetime import datetime, timezone
def validate_timestamp_range(timestamp, min_ts=0, max_ts=253402300799):
"""
Validate timestamp is within acceptable range.
Args:
timestamp: Unix timestamp (seconds or milliseconds)
min_ts: Minimum allowed timestamp (default: Unix epoch)
max_ts: Maximum allowed timestamp (default: year 9999)
Returns:
dict: Validation result with 'valid' boolean and optional 'error'
"""
# Type validation
if not isinstance(timestamp, (int, float)):
return {'valid': False, 'error': 'Timestamp must be a number'}
# Detect precision and normalize to seconds
if timestamp > 10000000000: # Likely milliseconds
ts = timestamp / 1000
else:
ts = timestamp
# Finite check
if not (-float('inf') < ts < float('inf')):
return {'valid': False, 'error': 'Timestamp must be finite'}
# Range validation
if ts < min_ts:
return {
'valid': False,
'error': f'Timestamp {ts} is before minimum {min_ts}'
}
if ts > max_ts:
return {
'valid': False,
'error': f'Timestamp {ts} exceeds maximum {max_ts}'
}
return {'valid': True, 'normalized': ts}
# Usage
result = validate_timestamp_range(
1704067200,
min_ts=datetime(2024, 1, 1, tzinfo=timezone.utc).timestamp(),
max_ts=datetime(2024, 12, 31, tzinfo=timezone.utc).timestamp()
)
print(result)
# {'valid': True, 'normalized': 1704067200.0}
2. 형식 유효성 검사
타임스탬프 형식이 예상 패턴과 일치하는지 확인합니다.
정규식 패턴
// Timestamp format validators
const timestampFormats = {
// Unix timestamp - seconds (10 digits)
unixSeconds: /^[0-9]{10}$/,
// Unix timestamp - milliseconds (13 digits)
unixMilliseconds: /^[0-9]{13}$/,
// ISO 8601 basic format
iso8601: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/,
// ISO 8601 with timezone
iso8601Tz: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?([+-]\d{2}:\d{2}|Z)$/,
// RFC 3339
rfc3339: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/,
};
function validateTimestampFormat(timestamp, format = 'unixMilliseconds') {
const pattern = timestampFormats[format];
if (!pattern) {
return { valid: false, error: `Unknown format: ${format}` };
}
const timestampStr = String(timestamp);
if (!pattern.test(timestampStr)) {
return {
valid: false,
error: `Timestamp "${timestampStr}" does not match ${format} format`
};
}
return { valid: true };
}
// Usage examples
console.log(validateTimestampFormat(1704067200000, 'unixMilliseconds'));
// { valid: true }
console.log(validateTimestampFormat('2024-01-01T00:00:00Z', 'iso8601'));
// { valid: true }
console.log(validateTimestampFormat(170406720, 'unixMilliseconds'));
// { valid: false, error: 'Timestamp "170406720" does not match...' }
3. 정밀 감지
타임스탬프 정밀도를 자동으로 감지하고 검증합니다.
function detectTimestampPrecision(timestamp) {
const tsStr = String(timestamp);
const length = tsStr.length;
// Precision mapping
const precisionMap = {
10: { precision: 'seconds', multiplier: 1000 },
13: { precision: 'milliseconds', multiplier: 1 },
16: { precision: 'microseconds', multiplier: 0.001 },
19: { precision: 'nanoseconds', multiplier: 0.000001 }
};
const detected = precisionMap[length];
if (!detected) {
return {
valid: false,
error: `Unable to detect precision for ${length}-digit timestamp`
};
}
// Convert to milliseconds
const normalized = timestamp * detected.multiplier;
// Sanity check - should be between 1970 and year 3000
const minDate = 0; // 1970-01-01
const maxDate = 32503680000000; // 3000-01-01
if (normalized < minDate || normalized > maxDate) {
return {
valid: false,
error: `Timestamp ${timestamp} with ${detected.precision} precision is out of valid range`
};
}
return {
valid: true,
precision: detected.precision,
normalized: Math.floor(normalized),
original: timestamp
};
}
// Usage
console.log(detectTimestampPrecision(1704067200));
// { valid: true, precision: 'seconds', normalized: 1704067200000, original: 1704067200 }
console.log(detectTimestampPrecision(1704067200000));
// { valid: true, precision: 'milliseconds', normalized: 1704067200000, original: 1704067200000 }
4. 시간대 확인
시간대 정보가 존재하고 유효한지 확인합니다.
function validateTimezone(dateString) {
// Check if timezone designator is present
const hasTimezone = /Z|[+-]\d{2}:\d{2}$/.test(dateString);
if (!hasTimezone) {
return {
valid: false,
warning: 'No timezone information - timestamp is ambiguous'
};
}
// Extract timezone
const tzMatch = dateString.match(/(Z|[+-]\d{2}:\d{2})$/);
const timezone = tzMatch ? tzMatch[1] : null;
// Validate timezone offset range
if (timezone && timezone !== 'Z') {
const [sign, hours, minutes] = timezone.match(/([+-])(\d{2}):(\d{2})/).slice(1);
const offset = parseInt(sign + hours) * 60 + parseInt(sign + minutes);
// Valid timezone range: -12:00 to +14:00
if (offset < -720 || offset > 840) {
return {
valid: false,
error: `Invalid timezone offset: ${timezone}`
};
}
}
return {
valid: true,
timezone: timezone === 'Z' ? 'UTC' : timezone
};
}
// Usage
console.log(validateTimezone('2024-01-01T00:00:00Z'));
// { valid: true, timezone: 'UTC' }
console.log(validateTimezone('2024-01-01T00:00:00+05:30'));
// { valid: true, timezone: '+05:30' }
console.log(validateTimezone('2024-01-01T00:00:00'));
// { valid: false, warning: 'No timezone information - timestamp is ambiguous' }
보안 고려 사항
1. 시간 기반 공격 방지
// BAD: Vulnerable to timing attacks
function validateToken(token, expiryTimestamp) {
return Date.now() < expiryTimestamp;
}
// GOOD: Secure validation with constant-time comparison
function validateTokenSecure(token, expiryTimestamp) {
// Validate timestamp first
const validation = validateTimestampRange(expiryTimestamp, {
min: Date.now(),
max: Date.now() + 86400000 // Max 24 hours in future
});
if (!validation.valid) {
return false;
}
// Use constant-time comparison for sensitive checks
const now = Date.now();
const isValid = now < validation.normalized;
// Add jitter to prevent timing analysis
const jitter = Math.floor(Math.random() * 10);
return isValid;
}
2. 정수 오버플로 방지
function safeTimestampAddition(timestamp, milliseconds) {
// Check for potential overflow before adding
if (timestamp > Number.MAX_SAFE_INTEGER - milliseconds) {
throw new Error('Timestamp addition would cause overflow');
}
const result = timestamp + milliseconds;
// Verify result is within safe integer range
if (!Number.isSafeInteger(result)) {
throw new Error('Result timestamp exceeds safe integer range');
}
return result;
}
// Usage
try {
const future = safeTimestampAddition(1704067200000, 86400000);
console.log(future); // 1704153600000
} catch (error) {
console.error(error.message);
}
3. SQL 주입 방지
// BAD: Vulnerable to SQL injection
function getUserActivityUnsafe(userId, startTime) {
const query = `SELECT * FROM activities WHERE user_id = ${userId}
AND created_at > ${startTime}`;
return db.query(query); // DANGEROUS!
}
// GOOD: Using parameterized queries with validation
function getUserActivitySafe(userId, startTime) {
// Validate timestamp first
const validation = validateTimestampRange(startTime, {
min: 0,
max: Date.now()
});
if (!validation.valid) {
throw new Error(`Invalid timestamp: ${validation.error}`);
}
// Use parameterized query
const query = 'SELECT * FROM activities WHERE user_id = ? AND created_at > ?';
return db.query(query, [userId, new Date(validation.normalized)]);
}
완전한 검증 기능
다음은 모든 모범 사례를 결합한 프로덕션 준비 타임스탬프 유효성 검사기입니다.
class TimestampValidator {
constructor(options = {}) {
this.minDate = options.minDate || new Date('1970-01-01');
this.maxDate = options.maxDate || new Date('2099-12-31');
this.allowedFormats = options.formats || ['unix', 'iso8601'];
this.requireTimezone = options.requireTimezone || false;
}
validate(timestamp) {
const errors = [];
const warnings = [];
// Step 1: Type validation
if (timestamp === null || timestamp === undefined) {
errors.push('Timestamp is required');
return { valid: false, errors };
}
// Step 2: Format detection and parsing
let parsedDate;
let detectedFormat;
if (typeof timestamp === 'number') {
detectedFormat = 'unix';
const precision = detectTimestampPrecision(timestamp);
if (!precision.valid) {
errors.push(precision.error);
return { valid: false, errors };
}
parsedDate = new Date(precision.normalized);
} else if (typeof timestamp === 'string') {
// Try ISO 8601 parsing
parsedDate = new Date(timestamp);
detectedFormat = 'iso8601';
// Validate timezone if required
if (this.requireTimezone) {
const tzValidation = validateTimezone(timestamp);
if (!tzValidation.valid) {
if (tzValidation.error) {
errors.push(tzValidation.error);
} else if (tzValidation.warning) {
warnings.push(tzValidation.warning);
}
}
}
} else {
errors.push(`Invalid timestamp type: ${typeof timestamp}`);
return { valid: false, errors };
}
// Step 3: Check if format is allowed
if (!this.allowedFormats.includes(detectedFormat)) {
errors.push(`Format ${detectedFormat} is not allowed. Allowed: ${this.allowedFormats.join(', ')}`);
}
// Step 4: Validate parsed date is valid
if (isNaN(parsedDate.getTime())) {
errors.push('Timestamp could not be parsed to a valid date');
return { valid: false, errors };
}
// Step 5: Range validation
if (parsedDate < this.minDate) {
errors.push(`Timestamp ${parsedDate.toISOString()} is before minimum ${this.minDate.toISOString()}`);
}
if (parsedDate > this.maxDate) {
errors.push(`Timestamp ${parsedDate.toISOString()} exceeds maximum ${this.maxDate.toISOString()}`);
}
// Step 6: Additional checks
const timestamp_ms = parsedDate.getTime();
// Check for JavaScript Date edge cases
if (timestamp_ms === -62135596800000) {
warnings.push('Timestamp represents year 0 which may have parsing issues');
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
warnings: warnings.length > 0 ? warnings : undefined,
parsed: parsedDate,
format: detectedFormat,
timestamp: timestamp_ms
};
}
}
// Usage examples
const validator = new TimestampValidator({
minDate: new Date('2024-01-01'),
maxDate: new Date('2024-12-31'),
requireTimezone: true,
formats: ['unix', 'iso8601']
});
// Valid timestamp
console.log(validator.validate(1704067200000));
// {
// valid: true,
// parsed: Date,
// format: 'unix',
// timestamp: 1704067200000
// }
// Invalid - out of range
console.log(validator.validate(1735689600000)); // 2025-01-01
// {
// valid: false,
// errors: ['Timestamp exceeds maximum...']
// }
// Invalid - missing timezone
console.log(validator.validate('2024-06-15T10:30:00'));
// {
// valid: false,
// errors: ['No timezone information - timestamp is ambiguous']
// }
오류 처리 모범 사례
1. 오류 메시지 지우기
function formatValidationError(validation) {
if (validation.valid) {
return null;
}
return {
message: 'Timestamp validation failed',
details: validation.errors,
suggestions: [
'Ensure timestamp is in correct format (Unix milliseconds or ISO 8601)',
'Check timestamp is within valid range (1970-2099)',
'Include timezone information (Z or +HH:MM)',
'Use 13-digit Unix timestamp for millisecond precision'
]
};
}
2. 우아한 저하
function parseTimestampWithFallback(timestamp, fallback = null) {
const validation = validator.validate(timestamp);
if (validation.valid) {
return validation.parsed;
}
// Log error for monitoring
console.warn('Timestamp validation failed:', validation.errors);
// Return fallback value
return fallback ? new Date(fallback) : new Date();
}
유효성 검사기 테스트
// Test cases for timestamp validation
const testCases = [
// Valid cases
{ input: 1704067200000, expected: true, description: 'Unix milliseconds' },
{ input: '2024-01-01T00:00:00Z', expected: true, description: 'ISO 8601 UTC' },
// Invalid cases
{ input: null, expected: false, description: 'Null timestamp' },
{ input: 'invalid', expected: false, description: 'Invalid string' },
{ input: -1, expected: false, description: 'Negative timestamp' },
{ input: Infinity, expected: false, description: 'Infinity' },
{ input: NaN, expected: false, description: 'NaN' },
// Edge cases
{ input: 0, expected: true, description: 'Unix epoch' },
{ input: 253402300799000, expected: true, description: 'Max timestamp (year 9999)' },
];
testCases.forEach(test => {
const result = validator.validate(test.input);
const passed = result.valid === test.expected;
console.log(`${passed ? '✓' : '✗'} ${test.description}`);
if (!passed) {
console.log(' Expected:', test.expected, 'Got:', result.valid);
}
});
피해야 할 일반적인 함정
- 입력 유형을 확인하지 않음 - 처리하기 전에 항상 유형을 확인하세요.
- 시간대 정보 무시 - 근무 외 오류가 발생할 수 있음
- 느슨한 동등(==) 사용 - 비교를 위해 엄격한 동등(===)을 사용합니다.
- 특정 사례를 처리하지 않음 - 0, 음수 값, 매우 큰 숫자로 테스트
- 정밀도 차이 잊어버리기 - 초 vs 밀리초 vs 마이크로초
- 클라이언트 타임스탬프 신뢰 - 항상 서버 측에서 유효성을 검사합니다.
- 검증 실패를 기록하지 않음 - 디버깅을 어렵게 만듭니다.
결론
강력한 애플리케이션을 구축하려면 적절한 타임스탬프 유효성 검사가 필수적입니다. 다음 모범 사례를 따르면 됩니다.
- 항상 범위 유효성 검사 - 타임스탬프가 허용 가능한 범위 내에 있는지 확인하세요.
- 명시적으로 형식을 확인하세요 - 입력 형식을 가정하지 마세요.
- 시간대를 신중하게 처리 - 필요한 경우 시간대 정보를 요구합니다.
- 보안 영향 고려 - 조작 및 공격 방지
- 명확한 오류 메시지 제공 - 사용자와 개발자가 문제를 디버깅할 수 있도록 지원
- 철저하게 테스트 - 극단적인 경우와 유효하지 않은 입력을 다루세요.
이러한 유효성 검사 기술을 사용하여 잘못된 데이터, 보안 취약성 및 논리 오류로부터 애플리케이션을 보호하세요.