정보주체
Data Subject
개인정보의 주체가 되는 자연인. GDPR과 개인정보보호법에서 권리를 보장받는 핵심 대상.
Data Subject
개인정보의 주체가 되는 자연인. GDPR과 개인정보보호법에서 권리를 보장받는 핵심 대상.
정보주체(Data Subject)는 개인정보에 의해 식별되거나 식별될 수 있는 자연인을 의미합니다. GDPR 제4조 제1호는 정보주체를 "identified or identifiable natural person"으로 정의하며, 이름, 식별번호, 위치정보, 온라인 식별자 등 직간접적으로 개인을 특정할 수 있는 정보의 대상을 포함합니다. 한국 개인정보보호법 제2조 제3호에서도 "처리되는 정보에 의하여 알아볼 수 있는 사람"으로 유사하게 정의합니다.
정보주체는 자신의 개인정보에 대해 포괄적인 권리를 갖습니다. GDPR은 열람권(제15조), 정정권(제16조), 삭제권/잊힐 권리(제17조), 처리제한권(제18조), 이동권(제20조), 반대권(제21조), 자동화된 의사결정에 관한 권리(제22조)를 보장합니다. 한국 개인정보보호법도 제35조~제37조의2에서 열람, 정정/삭제, 처리정지, 자동화된 결정에 대한 설명요구권을 규정합니다.
AI 시스템에서 정보주체 권리는 특별한 의미를 가집니다. EU AI Act는 고위험 AI의 투명성 요건(제13조)에서 AI로 처리되는 정보주체에게 시스템 운영에 대한 이해 가능한 정보 제공을 요구합니다. GDPR 제22조는 오직 자동화된 처리에만 기반한 중대한 결정에서 정보주체를 보호하며, 인적 개입 요구, 의견 표명, 결정 이의 제기 권리를 부여합니다.
정보주체 권리 행사에 대한 대응 기한은 엄격히 규정됩니다. GDPR은 요청 접수 후 1개월 이내 응답을 요구하며(제12조 제3항), 복잡한 요청의 경우 2개월 연장이 가능합니다. 한국 개인정보보호법은 10일 이내 응답을 원칙으로 합니다(제35조 제3항). 권리 행사 거부 시에는 이유와 불복 방법을 정보주체에게 안내해야 하며, 부당한 거부는 GDPR 기준 최대 2,000만 유로 또는 연간 매출의 4%의 과징금 대상이 됩니다.
"""
정보주체 권리 요청(Data Subject Access Request) 처리 시스템
GDPR Chapter III 및 개인정보보호법 제35조~제37조 준수
"""
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
import uuid
class SubjectRight(Enum):
"""GDPR/개인정보보호법 정보주체 권리"""
ACCESS = "access" # Art 15 열람권 / 제35조
RECTIFICATION = "rectification" # Art 16 정정권 / 제36조
ERASURE = "erasure" # Art 17 삭제권 / 제36조
RESTRICTION = "restriction" # Art 18 처리제한권 / 제37조
PORTABILITY = "portability" # Art 20 이동권 / 제35조의2
OBJECTION = "objection" # Art 21 반대권 / 제37조
ADM_EXPLANATION = "adm_explanation" # Art 22 / 제37조의2
class RequestStatus(Enum):
"""요청 처리 상태"""
RECEIVED = "received"
IDENTITY_VERIFICATION = "identity_verification"
PROCESSING = "processing"
EXTENDED = "extended"
COMPLETED = "completed"
REJECTED = "rejected"
@dataclass
class DataSubject:
"""정보주체 정보"""
subject_id: str
email: str
name: str
verified: bool = False
verification_method: Optional[str] = None
jurisdiction: str = "GDPR" # GDPR or PIPA
@dataclass
class DSARRequest:
"""
정보주체 권리 요청(DSAR)
GDPR Art 12: 요청 접수 후 1개월(연장 시 3개월) 이내 응답
개인정보보호법: 10일 이내 응답
"""
request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
subject: DataSubject = None
right_type: SubjectRight = None
request_details: str = ""
received_at: datetime = field(default_factory=datetime.now)
status: RequestStatus = RequestStatus.RECEIVED
response_deadline: datetime = None
extended: bool = False
extension_reason: Optional[str] = None
completed_at: Optional[datetime] = None
rejection_reason: Optional[str] = None
def __post_init__(self):
"""관할권에 따른 응답 기한 설정"""
if self.subject and not self.response_deadline:
if self.subject.jurisdiction == "PIPA":
# 개인정보보호법: 10일 이내
self.response_deadline = self.received_at + timedelta(days=10)
else:
# GDPR: 1개월 이내
self.response_deadline = self.received_at + timedelta(days=30)
class DSARHandler:
"""
정보주체 권리 요청 처리기
GDPR Art 12 및 개인정보보호법 응답 기한 준수
"""
# GDPR Art 12(5): 권리 행사 거부 가능 사유
VALID_REJECTION_REASONS = {
"manifestly_unfounded": "명백히 근거 없는 요청",
"excessive_repetition": "과도하게 반복적인 요청",
"identity_unverified": "본인 확인 불가",
"legal_obligation": "법적 보관 의무",
"legal_claim": "법적 청구권 행사/방어",
"public_interest": "공익적 보관 목적"
}
# 권리별 법적 근거
RIGHT_LEGAL_BASIS = {
SubjectRight.ACCESS: {
"GDPR": "제15조(열람권)",
"PIPA": "제35조(개인정보의 열람)"
},
SubjectRight.RECTIFICATION: {
"GDPR": "제16조(정정권)",
"PIPA": "제36조(개인정보의 정정/삭제)"
},
SubjectRight.ERASURE: {
"GDPR": "제17조(삭제권/잊힐 권리)",
"PIPA": "제36조(개인정보의 정정/삭제)"
},
SubjectRight.RESTRICTION: {
"GDPR": "제18조(처리제한권)",
"PIPA": "제37조(개인정보의 처리정지)"
},
SubjectRight.PORTABILITY: {
"GDPR": "제20조(이동권)",
"PIPA": "제35조의2(개인정보 전송요구권)"
},
SubjectRight.ADM_EXPLANATION: {
"GDPR": "제22조(자동화된 의사결정)",
"PIPA": "제37조의2(자동화된 결정에 대한 설명요구 등)"
}
}
def __init__(self):
self.requests: dict[str, DSARRequest] = {}
def submit_request(
self,
subject: DataSubject,
right_type: SubjectRight,
details: str
) -> DSARRequest:
"""정보주체 권리 요청 접수"""
request = DSARRequest(
subject=subject,
right_type=right_type,
request_details=details
)
self.requests[request.request_id] = request
return request
def verify_identity(
self,
request_id: str,
verification_method: str
) -> bool:
"""
본인 확인 (GDPR Art 12(6))
합리적 의심이 있는 경우 추가 정보 요청 가능
"""
request = self.requests.get(request_id)
if not request:
return False
valid_methods = ["government_id", "two_factor", "knowledge_based"]
if verification_method in valid_methods:
request.subject.verified = True
request.subject.verification_method = verification_method
request.status = RequestStatus.PROCESSING
return True
return False
def extend_deadline(
self,
request_id: str,
reason: str
) -> dict:
"""
응답 기한 연장 (GDPR Art 12(3))
복잡성/요청 수로 인해 최대 2개월 연장 가능
"""
request = self.requests.get(request_id)
if not request:
return {"success": False, "error": "요청을 찾을 수 없음"}
# GDPR만 연장 허용 (개인정보보호법은 연장 규정 없음)
if request.subject.jurisdiction != "GDPR":
return {
"success": False,
"error": "개인정보보호법은 기한 연장 규정 없음"
}
if request.extended:
return {"success": False, "error": "이미 연장됨"}
# 연장 사유 검증
valid_reasons = ["request_complexity", "high_volume"]
if reason not in valid_reasons:
return {"success": False, "error": "유효하지 않은 연장 사유"}
# 최대 2개월 추가 연장
request.response_deadline += timedelta(days=60)
request.extended = True
request.extension_reason = reason
request.status = RequestStatus.EXTENDED
return {
"success": True,
"new_deadline": request.response_deadline,
"notification_required": True, # 정보주체에게 연장 통지 필수
"notification_deadline": request.received_at + timedelta(days=30)
}
def process_request(
self,
request_id: str,
response_data: Optional[dict] = None,
rejection_reason: Optional[str] = None
) -> dict:
"""정보주체 요청 처리 완료"""
request = self.requests.get(request_id)
if not request:
return {"success": False, "error": "요청을 찾을 수 없음"}
# 본인 확인 검증
if not request.subject.verified:
return {
"success": False,
"error": "본인 확인 필요 (Art 12(6))"
}
now = datetime.now()
# 기한 초과 경고
deadline_warning = None
if now > request.response_deadline:
deadline_warning = (
f"응답 기한 초과: {(now - request.response_deadline).days}일 지연 - "
"감독당국 민원 및 과징금 위험"
)
if rejection_reason:
# 거부 처리
if rejection_reason not in self.VALID_REJECTION_REASONS:
return {
"success": False,
"error": "유효하지 않은 거부 사유"
}
request.status = RequestStatus.REJECTED
request.rejection_reason = rejection_reason
request.completed_at = now
return {
"success": True,
"status": "rejected",
"reason": self.VALID_REJECTION_REASONS[rejection_reason],
"appeal_info": "감독기관(DPA) 민원 제기 및 사법적 구제 가능",
"deadline_warning": deadline_warning
}
# 정상 처리
request.status = RequestStatus.COMPLETED
request.completed_at = now
legal_basis = self.RIGHT_LEGAL_BASIS.get(
request.right_type, {}
).get(request.subject.jurisdiction, "해당 없음")
return {
"success": True,
"status": "completed",
"legal_basis": legal_basis,
"processing_time_days": (now - request.received_at).days,
"response_data": response_data,
"deadline_warning": deadline_warning
}
def get_pending_requests(self) -> list[dict]:
"""기한 임박/초과 요청 조회"""
now = datetime.now()
alerts = []
for req in self.requests.values():
if req.status in [RequestStatus.COMPLETED, RequestStatus.REJECTED]:
continue
days_remaining = (req.response_deadline - now).days
if days_remaining < 0:
alerts.append({
"request_id": req.request_id,
"right_type": req.right_type.value,
"alert": "OVERDUE",
"days_overdue": abs(days_remaining)
})
elif days_remaining <= 5:
alerts.append({
"request_id": req.request_id,
"right_type": req.right_type.value,
"alert": "URGENT",
"days_remaining": days_remaining
})
return sorted(alerts, key=lambda x: x.get("days_overdue", -x.get("days_remaining", 0)), reverse=True)
# 사용 예시
if __name__ == "__main__":
handler = DSARHandler()
# 정보주체 생성
subject = DataSubject(
subject_id="DS-2024-001",
email="user@example.com",
name="홍길동",
jurisdiction="PIPA" # 한국 개인정보보호법 적용
)
# 열람 요청 제출
request = handler.submit_request(
subject=subject,
right_type=SubjectRight.ACCESS,
details="최근 1년간 수집된 모든 개인정보 열람 요청"
)
print(f"요청 ID: {request.request_id}")
print(f"응답 기한: {request.response_deadline}") # 10일 후
# 출력: 응답 기한: (10일 후 날짜)