GDPR, PCI-DSS, SOC 2가 인증 시스템에 부과하는 구체적 요구사항, NIST 800-63B 비밀번호 가이드라인, 감사 로깅 설계, MFA 요구사항, 데이터 거주 전략을 정리합니다.
인증 시스템은 기술적 설계만으로 완성되지 않습니다. GDPR, PCI-DSS, SOC 2 등 각종 규제가 인증과 접근 제어에 구체적 요구사항을 부과하며, 이를 충족하지 못하면 막대한 과징금과 사업 리스크가 발생합니다. 이번 장에서는 주요 규제가 인증 시스템에 요구하는 사항을 정리하고, 기술적으로 어떻게 대응하는지 살펴봅니다.
| 규제 | 대상 | 인증 관련 핵심 요구 |
|---|---|---|
| GDPR | EU 시민 데이터를 다루는 모든 조직 | 접근 제어, 동의 관리, 데이터 최소화 |
| PCI-DSS | 카드 결제를 처리하는 조직 | 강력한 인증, 접근 로깅, 정기 검토 |
| SOC 2 | 클라우드 서비스 제공자 | 접근 통제, 모니터링, 변경 관리 |
| NIST 800-63B | 미국 연방 기관 (사실상 글로벌 표준) | 비밀번호 정책, MFA, 인증 수준 |
| 개인정보보호법 | 한국 내 개인정보 처리자 | 접근 권한 관리, 접속 기록 보관 |
GDPR(General Data Protection Regulation)은 EU 시민의 개인정보를 보호하는 규정으로, 전 세계 어디서든 EU 시민의 데이터를 다루면 적용됩니다.
// GDPR Article 25: Data Protection by Design
// 인증 시스템에서 개인정보 최소 수집 원칙 적용
interface GdprCompliantUserData {
// 필수: 인증에 꼭 필요한 정보만 수집
id: string;
email: string;
passwordHash: string;
// 선택: 명시적 동의 후에만 수집
name?: string;
phoneNumber?: string;
// GDPR 관련 메타데이터
consentGiven: boolean;
consentTimestamp: Date;
consentVersion: string; // 동의한 약관 버전
dataProcessingBasis: "consent" | "legitimate_interest" | "contract";
}
// GDPR Article 17: 잊혀질 권리 (삭제 요청 처리)
async function handleDeletionRequest(userId: string): Promise<void> {
// 1. 사용자 인증 데이터 삭제
await deleteUserCredentials(userId);
// 2. 세션/토큰 전체 무효화
await revokeAllSessions(userId);
await revokeAllTokens(userId);
// 3. 개인 식별 정보 삭제 또는 익명화
await anonymizeUserData(userId);
// 4. 감사 로그는 보존 (법적 의무)
// 단, 개인 식별 정보는 익명화
await anonymizeAuditLogs(userId);
// 5. 삭제 완료 기록
await logDeletionRequest(userId, new Date());
}
// GDPR Article 20: 데이터 이동권
async function exportUserData(userId: string): Promise<UserDataExport> {
const userData = await getUserById(userId);
const authHistory = await getAuthHistory(userId);
const consentRecords = await getConsentRecords(userId);
return {
personalData: {
email: userData.email,
name: userData.name,
createdAt: userData.createdAt,
},
authenticationHistory: authHistory.map((entry) => ({
timestamp: entry.timestamp,
method: entry.method,
success: entry.success,
ipAddress: entry.ipAddress, // 개인정보에 해당
})),
consentHistory: consentRecords,
exportedAt: new Date(),
format: "JSON",
};
}GDPR 위반 시 과징금은 전 세계 연 매출의 4% 또는 2,000만 유로 중 큰 금액입니다. 인증 시스템에서 가장 빈번한 GDPR 위반은 과도한 개인정보 수집, 부적절한 동의 관리, 삭제 요청 미이행입니다.
PCI-DSS(Payment Card Industry Data Security Standard)는 카드 결제를 처리하는 모든 조직에 적용되는 보안 표준입니다. 인증에 대해 매우 구체적이고 엄격한 요구사항을 부과합니다.
// PCI-DSS Requirement 8: 인증 관련 주요 요구사항
interface PciCompliantPasswordPolicy {
// 8.3.6: 최소 12자 (또는 시스템이 지원하지 않으면 8자)
minLength: 12;
// 8.3.6: 숫자 + 알파벳 포함
requireNumeric: true;
requireAlphabetic: true;
// 8.3.7: 지난 4개 비밀번호와 동일 불가
passwordHistory: 4;
// 8.3.9: 90일마다 변경 (MFA 미사용 시)
// NIST는 주기적 변경을 권장하지 않지만, PCI-DSS는 아직 요구
maxAgeDays: 90;
// 8.3.4: 6회 실패 시 계정 잠금
maxFailedAttempts: 6;
lockoutDurationMinutes: 30;
}
// PCI-DSS 8.4: 카드 데이터 접근 시 MFA 필수
interface PciMfaRequirement {
// 관리 콘솔 접근 시 MFA 필수
adminAccess: "mfa_required";
// 카드 데이터 환경(CDE) 원격 접근 시 MFA 필수
remoteAccessToCde: "mfa_required";
// 비콘솔(네트워크) 관리 접근 시 MFA 필수
nonConsoleAdminAccess: "mfa_required";
}
// PCI-DSS 8.6: 시스템 계정/서비스 계정 관리
async function validateServiceAccount(
accountId: string,
): Promise<ComplianceResult> {
const account = await getServiceAccount(accountId);
const issues: string[] = [];
// 대화형 로그인이 가능한 서비스 계정은 금지
if (account.interactiveLoginAllowed) {
issues.push("서비스 계정의 대화형 로그인이 허용되어 있습니다.");
}
// 비밀번호/키의 정기 로테이션 확인
const daysSinceRotation = daysBetween(
account.lastKeyRotation,
new Date()
);
if (daysSinceRotation > 90) {
issues.push(`키 로테이션이 ${daysSinceRotation}일 전에 수행되었습니다.`);
}
return {
compliant: issues.length === 0,
issues,
checkedAt: new Date(),
};
}// PCI-DSS Requirement 10: 모든 접근 추적 및 모니터링
interface PciAuditEvent {
// 10.2.1: 누가
userId: string;
userName: string;
// 10.2.1: 무엇을
eventType:
| "login_success"
| "login_failure"
| "logout"
| "privilege_escalation"
| "account_creation"
| "account_modification"
| "account_deletion"
| "password_change"
| "mfa_enrollment"
| "data_access"
| "admin_action";
// 10.2.1: 언제
timestamp: Date;
// 10.2.1: 어디서
sourceIp: string;
userAgent: string;
// 10.2.1: 어떤 리소스에
resourceType: string;
resourceId: string;
// 결과
outcome: "success" | "failure";
reason?: string;
}
// 10.5: 감사 로그 무결성 보호
class TamperProofAuditLog {
private previousHash: string = "";
async writeEvent(event: PciAuditEvent): Promise<void> {
// 각 로그 엔트리에 이전 엔트리의 해시를 포함 (체인)
const entry = {
...event,
previousHash: this.previousHash,
entryHash: "",
};
// 현재 엔트리의 해시 계산
entry.entryHash = await this.calculateHash(entry);
this.previousHash = entry.entryHash;
// 쓰기 전용 저장소에 기록
await appendToImmutableStore(entry);
}
private async calculateHash(entry: object): Promise<string> {
const data = JSON.stringify(entry);
const encoder = new TextEncoder();
const hashBuffer = await crypto.subtle.digest(
"SHA-256",
encoder.encode(data),
);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
}SOC 2(Service Organization Control 2)는 클라우드 서비스 제공자의 보안, 가용성, 처리 무결성, 기밀성, 개인정보 보호를 평가하는 프레임워크입니다.
SOC 2에서 인증 관련 핵심 통제 항목입니다.
| 통제 ID | 요구사항 | 기술적 구현 |
|---|---|---|
| CC6.1 | 논리적 접근 제어 구현 | RBAC/ReBAC, MFA, 세션 관리 |
| CC6.2 | 신규/변경/퇴직 시 접근 관리 | 자동 프로비저닝/디프로비저닝 |
| CC6.3 | 역할 기반 최소 권한 적용 | 정기 접근 리뷰, 직무 분리 |
| CC6.5 | 접근 활동 모니터링 | 감사 로그, 이상 탐지 알림 |
// SOC 2 CC6.3: 정기적 접근 리뷰 자동화
interface AccessReviewItem {
userId: string;
userName: string;
department: string;
roles: string[];
lastLogin: Date | null;
lastReviewDate: Date;
reviewer: string;
status: "pending" | "approved" | "revoked" | "expired";
}
async function generateAccessReview(): Promise<AccessReviewItem[]> {
const allUsers = await getAllActiveUsers();
const reviewItems: AccessReviewItem[] = [];
for (const user of allUsers) {
// 90일 이상 로그인하지 않은 계정 플래그
const isInactive = user.lastLogin
? daysBetween(user.lastLogin, new Date()) > 90
: true;
// 권한이 부서/역할에 맞지 않는 경우 플래그
const expectedRoles = await getExpectedRoles(
user.department,
user.jobTitle,
);
const hasExcessRoles = user.roles.some(
(role) => !expectedRoles.includes(role)
);
if (isInactive || hasExcessRoles) {
reviewItems.push({
userId: user.id,
userName: user.name,
department: user.department,
roles: user.roles,
lastLogin: user.lastLogin,
lastReviewDate: user.lastAccessReview,
reviewer: user.manager ?? "security-team",
status: "pending",
});
}
}
return reviewItems;
}
// 퇴직자 접근 즉시 해제
async function handleEmployeeOffboarding(
employeeId: string,
): Promise<void> {
// 1. 모든 활성 세션 종료
await terminateAllSessions(employeeId);
// 2. 모든 토큰 폐기
await revokeAllTokens(employeeId);
// 3. 계정 비활성화 (삭제가 아닌 비활성화)
await deactivateAccount(employeeId);
// 4. 패스키/보안 키 등록 해제
await removeAllCredentials(employeeId);
// 5. 서드파티 앱 연동 해제
await revokeAllOAuthGrants(employeeId);
// 6. 감사 로그 기록
await logAuditEvent({
eventType: "account_deactivation",
userId: employeeId,
reason: "employee_offboarding",
performedBy: "hr-system",
timestamp: new Date(),
});
}NIST SP 800-63B는 디지털 인증 가이드라인으로, 전통적인 비밀번호 정책의 많은 관행을 뒤집었습니다.
| 항목 | 전통적 정책 | NIST 800-63B 권장 |
|---|---|---|
| 최소 길이 | 8자 | 8자 (최대 64자 이상 허용) |
| 복잡성 규칙 | 대소문자, 숫자, 특수문자 필수 | 복잡성 규칙 제거 |
| 주기적 변경 | 60~90일마다 | 유출 확인 시에만 변경 |
| 비밀번호 힌트 | 허용 | 금지 |
| 유출 비밀번호 차단 | 없음 | 필수 (침해 DB 대조) |
| 붙여넣기 허용 | 금지하는 경우 많음 | 반드시 허용 |
NIST가 주기적 비밀번호 변경을 폐지한 이유는 명확합니다. 주기적 변경을 강제하면 사용자는 "Password1!", "Password2!", "Password3!" 같은 예측 가능한 패턴을 사용하게 되어 오히려 보안이 약화됩니다. 대신 유출된 비밀번호 DB와 대조하여 알려진 취약 비밀번호를 차단하는 것이 훨씬 효과적입니다.
interface PasswordValidationResult {
valid: boolean;
errors: string[];
}
async function validatePassword(
password: string,
userId: string,
): Promise<PasswordValidationResult> {
const errors: string[] = [];
// NIST: 최소 8자, 최대 제한 없음 (64자 이상 허용)
if (password.length < 8) {
errors.push("비밀번호는 최소 8자 이상이어야 합니다.");
}
// NIST: 유출된 비밀번호 차단 (Have I Been Pwned API 등)
const isBreached = await checkBreachedPasswords(password);
if (isBreached) {
errors.push(
"이 비밀번호는 데이터 유출에서 발견되었습니다. 다른 비밀번호를 사용해주세요."
);
}
// NIST: 컨텍스트 기반 차단
const user = await getUserById(userId);
const contextWords = [
user.email.split("@")[0],
user.name,
"password",
"12345678",
].filter(Boolean);
for (const word of contextWords) {
if (password.toLowerCase().includes(word.toLowerCase())) {
errors.push("비밀번호에 개인정보나 일반적인 단어를 포함할 수 없습니다.");
break;
}
}
// NIST: 공백 허용, 유니코드 허용
// 복잡성 규칙 (대문자/소문자/숫자/특수문자 강제)은 적용하지 않음
return {
valid: errors.length === 0,
errors,
};
}
// Have I Been Pwned API를 활용한 유출 비밀번호 확인
async function checkBreachedPasswords(
password: string,
): Promise<boolean> {
const hash = await sha1(password);
const prefix = hash.substring(0, 5);
const suffix = hash.substring(5).toUpperCase();
// k-Anonymity: 해시의 앞 5자만 전송
const response = await fetch(
`https://api.pwnedpasswords.com/range/${prefix}`
);
const text = await response.text();
// 응답에서 나머지 해시 매칭 확인
return text.split("\n").some((line) => line.startsWith(suffix));
}여러 규제에서 요구하는 MFA 조건을 통합하면 다음과 같습니다.
interface MfaPolicy {
// 어떤 상황에서 MFA를 요구하는가
triggers: MfaTrigger[];
// 허용하는 MFA 방식
allowedMethods: MfaMethod[];
// 금지하는 MFA 방식
prohibitedMethods: MfaMethod[];
}
type MfaTrigger =
| "all_logins" // 모든 로그인
| "admin_access" // 관리자 접근
| "sensitive_data" // 민감 데이터 접근
| "new_device" // 새 디바이스
| "unusual_location" // 비정상 위치
| "high_risk_action"; // 고위험 작업
type MfaMethod =
| "passkey" // 패스키 (가장 강력)
| "security_key" // 하드웨어 보안 키
| "authenticator_app" // TOTP 앱
| "push_notification" // 푸시 알림
| "sms_otp" // SMS OTP
| "email_otp"; // 이메일 OTP
const regulatoryMfaPolicies: Record<string, MfaPolicy> = {
"pci-dss": {
triggers: ["admin_access", "sensitive_data"],
allowedMethods: [
"passkey",
"security_key",
"authenticator_app",
"push_notification",
],
// PCI-DSS 4.0: SMS는 피싱 위험으로 비권장
prohibitedMethods: ["sms_otp"],
},
"soc2": {
triggers: [
"admin_access",
"new_device",
"unusual_location",
],
allowedMethods: [
"passkey",
"security_key",
"authenticator_app",
"push_notification",
"sms_otp",
],
prohibitedMethods: [],
},
"nist-aal2": {
triggers: ["all_logins"],
allowedMethods: [
"passkey",
"security_key",
"authenticator_app",
],
// NIST AAL2: SMS OTP는 RESTRICTED (제한적 허용)
prohibitedMethods: ["email_otp"],
},
};규제에 따라 인증 데이터가 특정 지역에만 저장되어야 할 수 있습니다. GDPR은 EU 외부로의 데이터 전송에 제한을 두고, 한국 개인정보보호법도 유사한 요구가 있습니다.
데이터 거주 요구사항을 충족하는 가장 실용적인 접근은 리전별 독립 배포입니다. 각 리전에 독립적인 IdP 인스턴스를 운영하고, 글로벌 라우터가 사용자의 리전을 식별하여 적절한 인스턴스로 안내합니다. Keycloak은 렐름 간 데이터 격리가 가능하므로 리전별 렐름 운영도 선택지입니다.
규제 감사에 대비하여 인증 시스템이 충족해야 할 핵심 항목을 정리합니다.
interface ComplianceCheckResult {
category: string;
requirement: string;
status: "pass" | "fail" | "partial" | "not_applicable";
evidence: string;
remediation?: string;
}
async function runComplianceCheck(): Promise<ComplianceCheckResult[]> {
const results: ComplianceCheckResult[] = [];
// 인증 정책
results.push(await checkMfaEnforcement());
results.push(await checkPasswordPolicy());
results.push(await checkSessionTimeout());
results.push(await checkAccountLockout());
// 접근 제어
results.push(await checkRbacImplementation());
results.push(await checkLeastPrivilege());
results.push(await checkAccessReviewProcess());
results.push(await checkOffboardingProcess());
// 로깅과 모니터링
results.push(await checkAuditLogCompleteness());
results.push(await checkLogRetention());
results.push(await checkLogIntegrity());
results.push(await checkAlertConfiguration());
// 데이터 보호
results.push(await checkEncryptionAtRest());
results.push(await checkEncryptionInTransit());
results.push(await checkDataResidency());
// 인증서/키 관리
results.push(await checkCertificateExpiry());
results.push(await checkKeyRotation());
results.push(await checkSecretManagement());
return results;
}| 분류 | 점검 항목 | GDPR | PCI-DSS | SOC 2 |
|---|---|---|---|---|
| 인증 | MFA 적용 | 권장 | 필수 | 필수 |
| 인증 | 비밀번호 정책 | - | 필수 | 필수 |
| 인증 | 세션 타임아웃 | - | 필수 (15분) | 필수 |
| 인증 | 계정 잠금 | - | 필수 (6회) | 필수 |
| 접근제어 | 최소 권한 | 필수 | 필수 | 필수 |
| 접근제어 | 정기 리뷰 | - | 반기 | 연간 |
| 로깅 | 인증 이벤트 기록 | 필수 | 필수 | 필수 |
| 로깅 | 로그 보존 기간 | - | 1년 | 1년 |
| 데이터 | 암호화 (저장) | 필수 | 필수 | 필수 |
| 데이터 | 암호화 (전송) | 필수 | TLS 1.2+ | 필수 |
| 데이터 | 데이터 거주 | 조건부 | - | - |
규제 대응은 보안의 "바닥"을 정의합니다. GDPR은 개인정보의 최소 수집과 삭제 권리를, PCI-DSS는 결제 환경의 엄격한 인증과 로깅을, SOC 2는 서비스 전반의 접근 통제를 요구합니다. NIST 800-63B는 과학적 근거에 기반한 현대적 비밀번호 정책을 제시합니다.
이러한 규제 요구사항은 별도의 특수한 기능이 아니라, 올바르게 설계된 인증 시스템이라면 자연스럽게 충족되어야 하는 기본 사항입니다. 다음 장에서는 이 시리즈에서 다룬 모든 개념을 통합하여 현대적 인증 시스템을 처음부터 구축하는 실전 프로젝트를 진행합니다.
이 글이 도움이 되셨나요?
API 키 관리, OAuth Client Credentials, mTLS를 활용한 서비스 간 인증, 마이크로서비스에서의 JWT 전파, API 게이트웨이 인증 패턴을 실전 코드와 함께 설명합니다.
Keycloak 기반 인증, 패스키 등록, OAuth 2.1 + PKCE 플로우, OpenFGA를 활용한 ReBAC, 토큰 관리, API 인증, 모니터링과 감사까지 통합하는 실전 프로젝트를 구축합니다.
제로 트러스트의 핵심 원칙과 인증의 관계를 분석하고, 지속적 검증, 디바이스 신뢰, 컨텍스트 기반 접근 제어, BeyondCorp 모델, 아이덴티티 인식 프록시를 설명합니다.