- RegisterSerializer에 비밀번호 정책 검증 추가 - ExtendPasswordExpiryView: 비밀번호 유효기간 연장 API - CustomTokenObtainPairSerializer: 로그인 시 만료/잠금 검증 - password_utils.py: 정책 검증, 계정 잠금, 만료 체크 유틸리티 - SiteSettings 모델에 비밀번호 정책 필드 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -14,14 +14,37 @@ class SiteSettings(models.Model):
|
||||
"""
|
||||
사이트 전역 설정 (싱글톤 패턴)
|
||||
- Google 로그인 활성화 여부 등 관리
|
||||
- 비밀번호 정책 설정
|
||||
"""
|
||||
# 소셜 로그인 설정
|
||||
google_login_enabled = models.BooleanField(default=True, verbose_name="Google 로그인 활성화")
|
||||
|
||||
# 향후 확장 가능한 설정들
|
||||
# kakao_login_enabled = models.BooleanField(default=False, verbose_name="카카오 로그인 활성화")
|
||||
# naver_login_enabled = models.BooleanField(default=False, verbose_name="네이버 로그인 활성화")
|
||||
# registration_enabled = models.BooleanField(default=True, verbose_name="회원가입 허용")
|
||||
# ========== 비밀번호 정책 설정 ==========
|
||||
# 비밀번호 길이
|
||||
password_min_length = models.IntegerField(default=8, verbose_name="최소 비밀번호 길이")
|
||||
password_max_length = models.IntegerField(default=128, verbose_name="최대 비밀번호 길이")
|
||||
|
||||
# 비밀번호 복잡성 요구사항
|
||||
password_require_uppercase = models.BooleanField(default=True, verbose_name="대문자 필수")
|
||||
password_require_lowercase = models.BooleanField(default=True, verbose_name="소문자 필수")
|
||||
password_require_digit = models.BooleanField(default=True, verbose_name="숫자 필수")
|
||||
password_require_special = models.BooleanField(default=True, verbose_name="특수문자 필수")
|
||||
password_special_chars = models.CharField(
|
||||
max_length=100,
|
||||
default="!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
verbose_name="허용 특수문자"
|
||||
)
|
||||
|
||||
# 비밀번호 만료 정책
|
||||
password_expiry_days = models.IntegerField(default=90, verbose_name="비밀번호 만료일 (0=무제한)")
|
||||
password_expiry_warning_days = models.IntegerField(default=14, verbose_name="만료 경고일 (만료 전 N일)")
|
||||
|
||||
# 비밀번호 이력 관리
|
||||
password_history_count = models.IntegerField(default=3, verbose_name="이전 비밀번호 재사용 금지 횟수 (0=제한없음)")
|
||||
|
||||
# 계정 잠금 정책
|
||||
login_max_failures = models.IntegerField(default=5, verbose_name="최대 로그인 실패 횟수 (0=무제한)")
|
||||
login_lockout_minutes = models.IntegerField(default=30, verbose_name="계정 잠금 시간(분)")
|
||||
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@ -124,6 +147,11 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||
encrypted_nhn_api_password = models.BinaryField(blank=True, null=True, verbose_name="NHN Cloud API Password (암호화)")
|
||||
nhn_storage_account = models.CharField(max_length=128, blank=True, null=True, verbose_name="NHN Cloud Storage Account")
|
||||
|
||||
# 🔒 비밀번호 보안 관련 필드
|
||||
password_changed_at = models.DateTimeField(blank=True, null=True, verbose_name="비밀번호 변경일시")
|
||||
login_failures = models.IntegerField(default=0, verbose_name="로그인 실패 횟수")
|
||||
locked_until = models.DateTimeField(blank=True, null=True, verbose_name="계정 잠금 해제 시간")
|
||||
|
||||
objects = CustomUserManager()
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
@ -339,3 +367,29 @@ class KVMServer(models.Model):
|
||||
if self.libvirt_uri:
|
||||
return self.libvirt_uri
|
||||
return f"qemu+ssh://{self.username}@{self.host}:{self.port}/system"
|
||||
|
||||
|
||||
# ============================================
|
||||
# 비밀번호 이력 관리
|
||||
# ============================================
|
||||
|
||||
class PasswordHistory(models.Model):
|
||||
"""
|
||||
사용자 비밀번호 변경 이력 (재사용 방지용)
|
||||
"""
|
||||
user = models.ForeignKey(
|
||||
CustomUser,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='password_history',
|
||||
verbose_name="사용자"
|
||||
)
|
||||
password_hash = models.CharField(max_length=255, verbose_name="비밀번호 해시")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "비밀번호 이력"
|
||||
verbose_name_plural = "비밀번호 이력"
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.email} - {self.created_at.strftime('%Y-%m-%d %H:%M')}"
|
||||
|
||||
Reference in New Issue
Block a user