Django 베타테스터 일괄 생성 완벽 가이드 (HMAC + Fernet 암호화 적용 + Admin 500 에러 트러블슈팅)
Django 베타테스터 일괄 생성은 어떻게 할 수 있을까요?
프로젝트를 진행하면서 내부 사용자 대상 베타 테스트를 진행할 때, 50명, 100명, 심지어 수백 명의 테스트 계정을 수동으로 생성하는 것은 현실적으로 불가능합니다.
관리자 한 명이 일일이 회원가입을 받고 승인하는 과정은 시간 낭비일 뿐 아니라, 사번(employee_no)과 이름 같은 민감 정보를 평문으로 저장하면 보안 사고로 직결됩니다.
그래서 우리는 Django shell 스크립트 한 방으로 해결했습니다.
HMAC-SHA256 해시 + Fernet(AES-128) 암호화를 적용하고, 즉시 승인(approved) 상태로 생성하는 실전용 Django 베타테스터 일괄 생성 스크립트를 공개합니다.
이 포스트에서는 초기 스크립트의 문제점, 수정된 완성 스크립트, Admin에서 500 에러가 발생한 트러블슈팅 과정, 그리고 안전하게 운영하는 방법까지 모두 다룹니다.

왜 베타테스터를 일괄 생성해야 하는가?
베타 테스트 단계에서는 최소 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)}개의 베타테스터 계정 생성 완료")이 스크립트의 문제점:
employee_no필드에 평문 사번이 그대로 저장됨 (HMAC 해시가 아님)CustomUser모델의EncryptedCharField가 적용되지 않아employee_no_plain과name이 제대로 암호화되지 않음- Admin에서 삭제 시 500 Internal Server Error 발생 (암호화 필드 처리 중 예외)
해결: 완성된 fix_beta_users.py 스크립트

아래는 실제 운영 중인 검증된 스크립트입니다.
두 단계로 구성되어 있습니다.
# 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단계)
- 스크립트 파일 생성
cat <<'EOF' > fix_beta_users.py
# 위 스크립트 전체 붙여넣기
EOF- Django shell에서 실행
cd /home/
python manage.py shell < fix_beta_users.py- 생성 확인
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 (/admin/accounts/customuser/)에서 계정을 선택하고 삭제 버튼을 누르면 500 Internal Server Error가 발생하고, 브라우저 콘솔에 POST ... 500이 찍힘.
원인 분석:
- Admin의
delete_selected액션이 실행될 때,CustomUser모델의save()또는 관련 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}")- 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 동작 방식)






