신나는 장고 DRF 과제
회원가입 기능과 로그인 기능을 만들었다
포스트맨에서 테스트해보기
회원가입
성공
로그인
아니 누가봐도 위에 있는 아이디 비밀번호랑 똑같은데
"비밀번호가 일치하지 않습니다"라고 어거지를 쓴다
내 눈이 잘못된건가 해서 수많은 시도를 하였으나
계속해서 400에러를 건네주는 친절한 장고
원인찾기
선생님이 문제가 발생했을때는 소거법으로 해결하라그랬어.
(내가 생각했을 때)제일 의심스러운 부분부터 확인했는데 순서는 이런식이었음
1. 비밀번호 맞는지 대조하는 로직 확인하기
2. 데이터베이스에 저장된 비밀번호 확인하기
3. 입력된 비밀번호 처리하는 로직 확인하기
여기서 끝났기 때문에 그 다음은 생각 안해봤다
처음 한 생각
비밀번호가 맞는지 확인하는 로직이 작성된 곳을 찾아보면 머라도 나오겠지?
일단 나는 뷰에서는
# 회원가입 View
class SignupView(generics.CreateAPIView):
serializer_class = SignupSerializer
# 로그인 View
class LoginView(TokenObtainPairView):
serializer_class = LoginSerializer
이렇게 전부 시리얼라이저에게 떠넘겼기 때문에
시리얼라이저를 조져본다
# 회원가입 Serializer
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ["username", "password", "email", "name", "nickname", "birthday", "gender", "bio"]
그냥 평범한데.
회원가입할 때 필요한 필드들을 나열하는 역할을 하는 것 뿐이라 얜 잘못 없을듯.
다음 시리얼라이저.
# 로그인 Serializer
class LoginSerializer(TokenObtainPairSerializer):
def validate(self, attrs): # attrs: 입력받은 데이터 (username, password)
try:
user = CustomUser.objects.get(username=attrs["username"]) # 입력받은 username과 db의 username이 일치하는 사용자를 찾아냄
except CustomUser.DoesNotExist:
raise serializers.ValidationError("그런 아이디는 없습니다.") # 일치하는 사용자가 없으면 None이 아니라 DoesNotExist가 반환되므로 예외처리
if not user.check_password(attrs["password"]): # 입력받은 password와 db에 저장된 사용자의 password와 일치하는지 확인
raise serializers.ValidationError("비밀번호가 일치하지 않습니다.") # 일치 안하면 예외 발생!!!
data = super().validate(attrs) # validate 메서드 호출해서 원래 검증 진행(JWT 토큰 생성됨)
return data
이거네.
완전 의심스럽게 생겼다.
"비밀번호가 일치하지 않습니다." 이거 장고 기능 사용해서 말했던 게 아니라 내가 발생시킨 예외였구나. 나를 화나게 했던 게 과거의 나였어
어쨌든 저 문구가 출력된다는 건, 예외 발생까지는 제대로 실행되면서 내려오고 있는거니까
if not user.check_password(attrs["password"]):
# 입력받은 password와 db에 저장된 사용자의 password와 일치하는지 확인
여기까지 동작은 제대로 하고 있다는 건데
아무리 봐도 로직이 잘못 작성된 것이 아니라
진짜로 입력받은 password와 db에 저장된 password가 다르다는 결론이 난다.
def check_password(self, raw_password):
"""
Return a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
"""
def setter(raw_password):
self.set_password(raw_password)
# Password hash upgrades shouldn't be considered password changes.
self._password = None
self.save(update_fields=["password"])
return check_password(raw_password, self.password, setter)
데이터베이스 accounts_customuser 테이블 확인해보기
잘 저장되고 있는데?
입력된 password와 db에 저장된 password를 비교하는 로직에 문제가 있어 보이는지? x
db에 저장된 password 에 문제가 있어 보이는지? x
그럼 이제 입력된 password가 어떻게 처리되고 있는지 확인해보기
내가 만든 CustomUser 모델
class CustomUser(AbstractUser):
GENDER_CHOICES = [("M", "남자"), ("F", "여자"), ("O", "다른거"),]
email = models.EmailField(unique=True)
name = models.CharField(max_length=10)
nickname = models.CharField(max_length=10)
birthday = models.DateField()
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.username
CustomUser 클래스는 AbstractUser 클래스를 상속받아서 만들었고
class AbstractUser(AbstractBaseUser, PermissionsMixin):
AbstractUser 클래스는 AbstractBaseUser 클래스를 상속받아서 만들었고
class AbstractBaseUser(models.Model):
password = models.CharField(_("password"), max_length=128)
def set_password(self, raw_password): # raw_password == 평문 비밀번호
self.password = make_password(raw_password) # 평문 비밀번호를 해싱해서 저장
self._password = raw_password # 얘는 메모리에 임시 저장. 나중에 password_validation.password_changed 에서 사용됨
AbstractBaseUser 클래스는 models.Model 클래스를 상속받아서 만들었는데…
오 다행히 여기서 password에 관련된 메서드가 등장
make_password 함수가 raw_password(평문 비밀번호)를 전달 받아서
뭔가를 한 다음에 self.password에 저장하고 있나보네
make_password 함수는 머하는 애일까
def make_password(password, salt=None, hasher="default"):
"""
Turn a plain-text password into a hash for database storage
Same as encode() but generate a new random salt. If password is None then
return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
which disallows logins. Additional random string reduces chances of gaining
access to staff or superuser accounts. See ticket #20079 for more info.
"""
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(
UNUSABLE_PASSWORD_SUFFIX_LENGTH
)
if not isinstance(password, (bytes, str)):
raise TypeError(
"Password must be a string or bytes, got %s." % type(password).__qualname__
)
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
return hasher.encode(password, salt)
'Trouble Shooting' 카테고리의 다른 글
가상환경 활성화 후 깃 명령어 안들어먹을때 (0) | 2024.09.15 |
---|---|
매우화가나는 마이그레이션 (0) | 2024.09.08 |