|

Django 베타테스터 일괄 생성 완벽 가이드 (HMAC + Fernet 암호화 적용 + Admin 500 에러 트러블슈팅)

Django 베타테스터 일괄 생성은 어떻게 할 수 있을까요?

프로젝트를 진행하면서 내부 사용자 대상 베타 테스트를 진행할 때, 50명, 100명, 심지어 수백 명의 테스트 계정을 수동으로 생성하는 것은 현실적으로 불가능用于以下方面

관리자 한 명이 일일이 회원가입을 받고 승인하는 과정은 시간 낭비일 뿐 아니라, 사번(employee_no)과 이름 같은 민감 정보를 평문으로 저장하면 보안 사고로 직결됩니다.

그래서 우리는 Django shell 스크립트 한 방으로 해결했습니다.
HMAC-SHA256 해시 + Fernet(AES-128) 암호화를 적용하고, 즉시 승인(approved) 상태로 생성하는 실전용 Django 베타테스터 일괄 생성 스크립트를 공개합니다.

在本帖中,我们将使用 초기 스크립트의 문제점, 수정된 완성 스크립트, Admin에서 500 에러가 발생한 트러블슈팅 과정안전하게 운영하는 방법까지 모두 다룹니다.

Django 베타테스터 일괄 생성 스크립트 실행 중 IntegrityError 및 평문 저장 문제 터미널 로그

왜 베타테스터를 일괄 생성해야 하는가?

베타 테스트 단계에서는 최소 50명 이상의 실제 사용자(사번 보유 직원)로 테스트를 해야 합니다.
수동으로 50명을 생성하려면:

  • 50번의 회원가입 + 이메일 확인
  • 관리자 승인 작업
  • 비밀번호 초기화 및 전달

이 과정만으로도 반나절 이상이 소요됩니다.
게다가 사번을 평문으로 저장하면 DB 유출 시 1차 피해가 발생합니다.

解决方案: Django shell + 암호화 로직을 결합한 자동화 스크립트

문제 발생: 초기 스크립트의 치명적 버그

처음에 작성한 스크립트는 다음과 같았습니다.

# create_beta_users.py (문제 버전)
from django.contrib.auth import get_user_model
from django.db import IntegrityError

User = get_user_model()

prefixes = [10000000, 20000000, 30000000, 40000000, 50000000]
count_per_prefix = 10
created_accounts = []

for prefix in prefixes:
    for i in range(count_per_prefix):
        emp_id = str(prefix + i)
        try:
            user_kwargs = {
                User.USERNAME_FIELD: emp_id,   # ← 여기서 평문 사번이 그대로 저장됨
            }
            # name 필드가 NOT NULL이면 주석 해제 필요
            # user_kwargs['name'] = f'베타테스터_{emp_id}'

            user = User(**user_kwargs)
            user.set_password(emp_id)          # 비밀번호 = 사번 (평문)
            user.save()

            created_accounts.append(emp_id)
            print(f" 생성 완료: 사번 {emp_id} / 비번 {emp_id}")

        except IntegrityError:
            print(f" 이미 존재하는 사번입니다: {emp_id}")
        except Exception as e:
            print(f" 생성 실패 ({emp_id}): {e}")

print(f" 총 {len(created_accounts)}개의 베타테스터 계정 생성 완료")

이 스크립트의 문제점:

  1. employee_no 字段到 평문 사번이 그대로 저장됨 (HMAC 해시가 아님)
  2. CustomUser 모델의 EncryptedCharField가 적용되지 않아 employee_no_plain名字이 제대로 암호화되지 않음
  3. Admin에서 삭제 시 500 Internal Server Error 발생 (암호화 필드 처리 중 예외)

해결: 완성된 fix_beta_users.py 스크립트

Django shell에서 fix_beta_users.py 실행 후 50개 베타테스터 계정 생성 완료 로그 화면

以下是 실제 운영 중인 검증된 스크립트
두 단계로 구성되어 있습니다.

# fix_beta_users.py (최종 검증 완료 버전)
from apps.accounts.models import CustomUser, AccountStatus
from apps.accounts.encryption import hmac_of
from django.utils import timezone

# ============================================
# 1단계: 잘못 생성된 계정(평문 저장) 삭제
# ============================================
# employee_no가 64자리 hex 문자열(0-9a-f)이 아닌 경우 = 평문으로 잘못 저장된 계정
bad = CustomUser.objects.exclude(employee_no__regex=r'^[0-9a-f]{64}$')
print(f"잘못 생성된 계정 {bad.count()}개 삭제 중...")
bad.delete()
print("삭제 완료\n")

# ============================================
# 2단계: 올바른 방식으로 재생성 (HMAC + Fernet + 즉시 승인)
# ============================================
prefixes = [10000000, 20000000, 30000000, 40000000, 50000000]
created = []

for prefix in prefixes:
    for i in range(10):
        emp_id = str(prefix + i)           # 예: 10000000, 10000001 ...
        hashed = hmac_of(emp_id)           # HMAC-SHA256 해시 생성 (64자 hex)

        # 이미 존재하는 경우 건너뛰기
        if CustomUser.objects.filter(employee_no=hashed).exists():
            print(f"이미 존재: {emp_id}")
            continue

        # CustomUser 생성 (암호화 필드 자동 처리)
        user = CustomUser(
            employee_no=hashed,                    # HMAC 해시 저장 (조회용)
            role='employee',                       # 기본 역할
            status=AccountStatus.APPROVED,         # 즉시 승인 상태
            approved_at=timezone.now(),            # 승인 일시 기록
        )
        user.employee_no_plain = emp_id   # EncryptedCharField → 자동 Fernet 암호화
        user.name = f'베타테스터'         # EncryptedCharField → 자동 Fernet 암호화
        user.set_password(emp_id)         # 비밀번호 = 사번 (PBKDF2 해시)
        user.save()

        created.append(emp_id)
        print(f"생성 완료: 사번 {emp_id} / 비번 {emp_id}")

print(f"\n총 {len(created)}개 계정 생성 완료")
print("=" * 50)

이 스크립트의 핵심 포인트:

  • hmac_of(emp_id) → Β 복호화 불가능한 해시로 저장 (보안 핵심)
  • user.employee_no_plain = emp_id → Β EncryptedCharField"(《世界人权宣言》) 자동으로 Fernet 암호화
  • status=AccountStatus.APPROVED → 관리자 승인 없이 즉시 로그인 가능
  • 예외 처리와 중복 체크로 안정성 확보

실행 방법 (3단계)

  1. 스크립트 파일 생성
   cat <<'EOF' > fix_beta_users.py
   # 위 스크립트 전체 붙여넣기
   EOF
  1. Django shell에서 실행
   cd /home/
   python manage.py shell < fix_beta_users.py
  1. 생성 확인
   python manage.py shell -c "
   from apps.accounts.models import CustomUser
   print(f'총 계정 수: {CustomUser.objects.count()}')
   print('샘플 5명:')
   for u in CustomUser.objects.all()[:5]:
       print(f'  {u.employee_no_plain} / {u.name} / {u.status}')
   "

트러블슈팅: Admin에서 500 에러가 발생할 때

Django Admin 500 Internal Server Error 화면과 shell 명령어로 해결하는 전후 비교 인포그래픽

증상:
Django Admin (/admin/accounts/customuser/)에서 계정을 선택하고 삭제 버튼을 누르면 500 Internal Server Error가 발생하고, 브라우저 콘솔에 POST ... 500이 찍힘.

원인 분석:

  • Admin의 delete_selected 액션이 실행될 때, CustomUser 모델의 保存() 또는 관련 signal이 트리거됨
  • employee_no가 평문으로 저장된 상태에서 hmac_of()나 암호화 로직이 실행되면서 예외 발생
  • EncryptedCharField"(《世界人权宣言》) from_db_value / get_prep_value에서 복호화 실패

해결 방법 (가장 빠르고 안전):

# 1. 잘못 생성된 계정만 정확히 삭제 (정규식으로 64자 hex가 아닌 것만)
python manage.py shell -c "
from apps.accounts.models import CustomUser
bad = CustomUser.objects.exclude(employee_no__regex=r'^[0-9a-f]{64}$')
count = bad.count()
print(f'삭제 대상: {count}개')
bad.delete()
print(f'{count}개 삭제 완료')
print(f'남은 계정: {CustomUser.objects.count()}개')
"

该命令使用 Admin을 거치지 않고 ORM으로 직접 삭제하기 때문에 500 에러가 발생하지 않습니다.

삭제 후 위 fix_beta_users.py를 다시 실행하면 깔끔하게 재생성됩니다.

로그인 테스트 방법

생성된 베타테스터 계정 정보:

사번 범위비밀번호상태
10000000 ~ 10000009동일APPROVED
20000000 ~ 20000009동일APPROVED
50000000 ~ 50000009동일APPROVED

로그인 화면

  • 사번: 10000000
  • 비밀번호: 10000000

바로 로그인되어 프로젝트의 모든 기능을 테스트할 수 있습니다.

Django 베타테스터 일괄 생성 시 보안 고려사항 및 확장 팁

비밀번호 정책 강화 (실제 운영 시)

    • 베타 단계에서는 사번 = 비밀번호로 편의성을 우선했지만,
    • 실제 서비스에서는 강력한 초기 비밀번호 + 이메일 전송 방식으로 변경하세요.

    대량 생성 시 성능

      • 1000명 이상 생성할 때는 bulk_create + set_password를 별도 처리하는 것이 좋습니다.
      • django.db.transaction.atomic()으로 묶어서 DB 부하를 줄이세요.

      로그 기록

           import logging
           logger = logging.getLogger(__name__)
           logger.info(f"베타테스터 생성 완료: {emp_id}")
        1. CSV 파일로부터 읽어오기
        • 사번 목록을 Excel/CSV로 받아서 처리하는 버전으로 확장 가능합니다.

        结论

        Django 베타테스터 일괄 생성은 단순한 편의 기능이 아닙니다.
        보안(암호화)과 운영 효율성(자동화)을 동시에 잡는 필수 기술

        내부 보안이 중요한 시스템에서는
        HMAC + Fernet 조합으로 “DB가 유출되어도 안전한” 구조를 만드는 것이 핵심입니다.

        위 스크립트를 그대로 복사해서 사용하시고,
        Admin 500 에러가 발생하면 shell 명령어로 직접 삭제하는 트러블슈팅을 기억하세요.

        이제 50명의 베타테스터가 준비되었습니다.
        실제 사용자 피드백을 받아 프로젝트를 더 완성도 있게 만들어 보세요!


        출처 및 참고 자료

        • Django 공식 문서: Customizing authentication (AbstractBaseUser, USERNAME_FIELD)
        • Django 공식 문서: django.db.models.query.QuerySet.exclude() 및 regex lookup
        • cryptography 라이브러리 문서: Fernet (symmetric encryption)
        • OWASP: Password Storage Cheat Sheet (PBKDF2 권장)
        • Django Admin actions 소스 코드 분석 (delete_selected 동작 방식)

        类似文章

        发表回复

        您的邮箱地址不会被公开。 必填项已用 * 标注