⚖️ AI 규제/윤리

정보주체

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%의 과징금 대상이 됩니다.

💻 코드 예제

Python GDPR/개인정보보호법 기반 정보주체 권리 요청(DSAR) 처리 시스템
"""
정보주체 권리 요청(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일 후 날짜)

💬 실무 대화

정보주체가 AI 추천 시스템에서 자신의 프로파일 삭제를 요청했습니다. 반드시 삭제해야 하나요?
GDPR 제17조 삭제권은 절대적 권리가 아닙니다. 법적 의무 준수, 공익적 목적, 법적 청구권 행사/방어 등의 경우 거부할 수 있습니다. 다만 거부 시에는 사유와 감독기관 민원 방법을 안내해야 합니다. AI 학습 데이터의 경우 익명화된 데이터는 개인정보가 아니므로 삭제 대상이 아닙니다.
개인정보보호법과 GDPR의 응답 기한 차이가 있나요?
네, 한국 개인정보보호법은 10일 이내 응답이 원칙입니다. GDPR은 1개월이지만 복잡성이나 요청 수가 많으면 2개월 연장이 가능합니다. 다만 연장 시 1개월 이내에 정보주체에게 연장 사실과 사유를 통지해야 합니다. 양쪽 규제를 모두 적용받는 경우 더 짧은 기한인 10일 기준으로 대응하는 것이 안전합니다.
GDPR에서 정보주체의 권리를 설명해주세요.
GDPR Chapter III에서 8가지 핵심 권리를 보장합니다. 열람권(제15조), 정정권(제16조), 삭제권/잊힐 권리(제17조), 처리제한권(제18조), 통지받을 권리(제19조), 이동권(제20조), 반대권(제21조), 자동화된 의사결정에 관한 권리(제22조)입니다. 컨트롤러는 이 권리들을 무상으로, 그리고 접근 가능한 방식으로 보장해야 합니다.
정보주체와 개인정보처리자의 차이는요?
정보주체는 개인정보의 대상이 되는 자연인, 즉 데이터가 "누구에 관한 것인지"를 나타냅니다. 개인정보처리자(컨트롤러)는 처리 목적과 수단을 결정하는 주체로, "누가 데이터를 어떻게 사용할지"를 결정합니다. 수탁자(프로세서)는 컨트롤러를 대신해 처리하는 자입니다. 법인은 정보주체가 될 수 없고, 오직 자연인만 정보주체입니다.
VALID_REJECTION_REASONS를 열거형으로 정의했는데, 동적으로 관리하는 게 좋지 않을까요?
이 사유들은 GDPR 제12조와 제17조에 명시된 법적 근거이므로 동적 관리보다 정적 정의가 적합합니다. 다만 관할권별로 차이가 있을 수 있으니 jurisdiction 파라미터에 따라 다른 사유 세트를 반환하는 메서드로 확장하는 것을 권장합니다. 한국 개인정보보호법의 거부 사유는 제36조 제2항에서 별도로 정의됩니다.
본인 확인 방식이 3가지만 있는데 충분한가요?
실무에서는 더 다양한 방식이 사용됩니다. 계정 로그인, 등록된 이메일 확인, SMS 인증, 공인인증서, 영상통화 등이 있습니다. GDPR은 특정 방식을 강제하지 않고 "합리적인" 확인만 요구합니다. 다만 민감정보 열람이나 대량 데이터 이동 요청의 경우 더 강화된 인증을 적용하는 것이 좋습니다. verification_level 속성을 추가해 요청 유형별 인증 수준을 관리하세요.

⚠️ 주의사항

📚 더 배우기