✔️ 이전 로그인 기능에는 아래와 같이 username
, email
, password
... 등의 필드가 존재하지만, username
을 이용하여 로그인을 할 수 있게 코드를 작성하였다. 하지만 요즘은 대부분 email
로 대부분의 도메인에 로그인 할 수 있는 기능이 있기 때문에 username
보다는 email
로 로그인하는 것이 더 유용할 것이다.
def login_view(request):
msg = None
is_ok = False
if request.method == "POST":
form = AuthenticationForm(request, request.POST)
template = "login.html"
if form.is_valid():
username = form.cleaned_data.get("username")
raw_password = form.cleaned_data.get("password")
user = authenticate(username=username, password=raw_password)
if user is not None:
login(request, user)
template = "index.html"
is_ok = True
else:
msg = "올바른 유저ID와 패스워드를 입력하세요."
else:
form = AuthenticationForm()
for visible in form.visible_fields():
visible.field.widget.attrs["placeholder"] = "유저ID" if visible.name == "username" else "패스워드"
visible.field.widget.attrs["class"] = "form-control"
return render(request, "login.html", {"form": form, "msg": msg, "is_ok": is_ok})
✔️ username
, password
만을 이용하여 로그인 할때는 AuthenticationForm()을 이용했다. AuthenticationForm()에는 기본적으로 username
, password
를 번들로 제공하기 때문에 사용 가능한 방법이었다.
✔️ 하지만 email
을 이용하여 로그인을 구현하기 위해선 AuthenticationForm()에 없는 email에 대한 필드가 있어야 하기 때문에 내가 정의한 Form으로 AuthenticationForm()을 대체할 생각이다.
# This is Form Login Form
class LoginForm(forms.Form):
email = forms.CharField(
max_length=100, required=True, widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "이메일"})
)
password = forms.CharField(
max_length=30, required=True, widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "패스워드"})
)
remember_me = forms.BooleanField(
widget=forms.CheckboxInput(attrs={"class": "custom-control-input", "id": "_loginRememberMe"}),
required=False,
disabled=False,
)
✔️ 회원가입에 사용하였던 form.py에 LoginForm을 작성해 주었다. 필드는 email
, password
, remember_me
3가지이며, remember_me
는 나중에 브라우저가 닫혀도 로그인을 유지할 수 있게 사용할 checkbox에 대한 form 필드이다.
❗️❗️ AuthenticationForm인 경우에는 form에 대한 class나 placeholder과 같은 속성을 적용할때 for 루프로 필드마다 각각 부여했지만, LoginForm은 상속받은 forms.Form에서 오버라이딩 된 widget을 이용하여 class나 placeholder를 따로 부여할 수 있다.
help_text의 경우도 widget에 상속되어 있어 사용할 필요 없다.
def login_view(request):
is_ok = False
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
email = form.cleaned_data.get("email")
raw_password = form.cleaned_data.get("password")
remember_me = form.cleaned_data.get("remember_me")
msg = "올바른 유저ID와 패스워드를 입력하세요."
try:
user = Users.objects.get(email=email)
except Users.DoesNotExist:
pass
else:
if user.check_password(raw_password):
msg = None
login(request, user)
is_ok = True
request.session["remember_me"] = remember_me
# 브라우저가 닫혔을 때, 세션 만료 시간 설정(edge에서 테스트 진행)
# if not remember_me:
# request.session.set_expirey(1)
else:
msg = None
form = LoginForm()
print("REMEMBER_ME: ", request.session.get("remember_me"))
return render(request, "login.html", {"form": form, "msg": msg, "is_ok": is_ok})
✔️ AuthenticationForm()이 내가 작성한 LoginForm() 으로, form에 대한 유효성 체크 시 username
을 email
로 변경해 주었다.
✔️ authenticate()함수로 자격증명이 된 user객체를 생성하였다면 이번에는 직접 ORM을 이용하여 email에 대한 user 객체를 불러왔다.
✔️ try except else를 이용하여 해당 email의 정보를 가진 계정이 존재하지 않으면 except로 넘어갈 수 있게 하며, 계정이 존재하는 경우에는 user 계정에 대한 password가 부합하는지 check_password()를 이용하여 True가 반환되었을 때 login 이 될 수 있게 코드를 작성해 주었다.
✔️ remember_me에대한 내용은 추후 다시 다루려고 한다.
❗️❗️ Caution
try except else로 분기 처리를 하게 된 이유는 user에 대한 객체를 조회 할때 get()을 사용하였는데 queryset조회 시 해당하는 정보가 없으면 api가 그대로 죽어버리기 때문에 try except를 걸어 에러발생시에도 로직의 방향을 핸들링해주었다.
💡 Tip
'유저 아이디가 없습니다.' OR '가입된 유저가 아닙니다.' OR '해당 이메일을 찾을 수 없습니다.' 와 같은 문구는 보안상의 이슈로 사용하지 않는 추세이다!🗣 개인적으로 사용자에게 어떠한 이유로 로그인이 되지 않았는지 문구를 알려주는 것이 좋다고 판단하고 있었는데, 보안상의 측면에서 나쁜의도를 가지고 찾아볼 수 있다고 하니 너무 상세한 안내문구는 또 다른 문제를 발생할 수 있겠구나 라는 생각이 들었다.
✔️ 참고로 위의 코드에서 로그인 완료 시 session을 이용하여 로그인 세션 만료에 대한 설정 코드를 작성해 놓았는데, 주석을 풀어 실행시키는 경우 위와 같이 브라우저에서없는 set_expirey라며 사용이 안되는 것을 확인 할 수 있다.
✔️ 이는 Chrome, Edge 등의 브라우저에서 session을 이용해서 만료에 대한 설정을 할 수 없게 되어 있는데, 이는 Django 특성상 하기 그렇다고 한다. 하지만 구현하는 방법은 다시 한 번 다룰 생각이니 지금은 넘어가도록 하자.