session으로 로그인 구현, Decorator

TeetyWoo·2021년 10월 16일
1

Django

목록 보기
4/11

프로젝트를 진행하는데, User모델에 대한 이해 없이 모델을 만들었다가 django.auth 관련 메소드를 쓸 수 없게 되었다. request.user로 확인해 봐도 Anonymous user가 나오는 상황.
django에서 user모델에 제약받지 않고 로그인을 구현할 방법을 찾다가, 쿠키 혹은 세션 로그인 방식을 찾았다.
다만 이 방식은 보안이슈와 request가 거대해 지는 단점이 있다. JWT 기반 로그인을 고려해 볼 것!

쿠키(Cookie)

쿠키란? :클라이언트인 웹 브라우저 로컬에 저장하는 키와 값이 들어있는 작은 데이터 파일
만료시점 :사용자 인증이 유효한 시간을 명시 가능, 브라우저가 종료되도 유효시간이 남아있으면 인증이 유지 됨

쿠키 구성 요소

이름 : 각각의 쿠키에 대한 식별자
값 : 쿠키의 이름과 관련된 값
유효시간 : 쿠키의 유지시간
도메인 : 쿠키를 전송할 도메인
경로 : 쿠키를 전송할 도메인
용량제한: 한 도메인 당 20개, 모든 도메인에 대해 300개 제한, 하나의 쿠키는 4KB
클라이언트도 모르게 접속되는 사이트에 의해서 설정 될 수 있기 때문에 쿠키로 인한 문제가 발생하는 것을 막고자 제한을 걸어놓았다. 하나의 도메인에서 쿠키가 20개를 초과하면 가장 적게 사용된 쿠키부터 삭제

쿠키 동작 방식

클라이언트가 페이지 요청->서버에서 쿠키 생성->HTTP 헤더에 쿠키를 포함시켜 응답
브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라언트에서 보관하고 있음
같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄->서버에서 쿠키를 읽어 이전 상태 정보를 변경할 필요가 있을 때 쿠키를 업데이트 하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답

ex)
방문 사이트에서 로그인 시, 아이디와 비밀번호를 저장하시겠습니까?
쇼핑몰의 장바구니 (비 로그인시 장바구니에 담았던 품목을 로그인 시 그대로 유지하기 위해서 사용)

세션(Session)

세션이란? :일정 시간 동안 같은 사용자, 브라우저로부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술

쿠키를 기반, 차이점은 사용자 정보를 브라우저에 저장하지 않고 서버에서 관리

만료시점 :서버에서는 클라이언트를 구분하기 위해서 세션 ID 를 부여하여 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증상태 유지

세션의 장점 :클라이언트의 정보를 서버에 저장하기 때문에 쿠키보다 보안에 우수
세션의 단점 :쿠키보다 보안 면에서 우수하지만 사용자가 많아질경우 서버 메모리를 많이 차지하게 됨 -> 성능 저하의 요인

세션의 동작 방식

클라이언트가 서버에 접속시 세션 ID 를 발급
클라이언트는 세션 ID 에 대해 쿠키를 사용하여 저장
클라이언트가 서버에 다시 접속 시 이 쿠키를 이용해서 세션 ID 값을 서버에 전달
로그인과 같이 보안 상 중요한 작업에 사용

  def post(self, request):
      loginform = LoginForm(request.POST)
      context = { 'forms' : loginform }
      if loginform.is_valid():
          self.request.session['user'] = loginform.cleaned_data['userID']
          username = self.request.session['user']
          # return super().form_valid(form)
          return render(request, './home.html', {'username' : username})

이런 식으로 request.session['user'] 라는 키값에 로그인 할때 템플릿에서 POST되는 'userID'라는 값을 넣어준다.
이때 ['user']는 내가 임의로 정한 값이므로 'user1'이든 'user2'든 상관없이 작동함

def new_shopRev(request):
    username = request.session.get('user')
    if User.objects.filter(userID = username).exists():

session에 들어간 값을 username이라는 변수로 지정해놓고, 내가 정의한 User모델의 pk값과 비교해서 존재한다면? 로그인 한 유저만을 대상으로 view를 구동시킬 수 있다 (로그인이 안되어 있으면 username은 None일 것이므로 filter에서 걸러진다)

다만 방식의 단점은 html 등에서 is_authenticated와 같은 django.auth기반 메소드들을 이용할 수 없다.
from django.contrib.auth.decorators import login_required도 사용할 수 없다

decorator, login_required

위의 문제를 해결하고자 직접 decorator.py에 login_required를 작성했다.
그걸 생성한 likeview에 적용

from django.utils.decorators import method_decorator
from .decorator import login_required

decorator란

어떤 함수를 받아 명령을 추가한 뒤 이를 다시 함수의 형태로 반환하는 함수.
어떤 함수의 내부를 수정하지 않고 기능에 변화를 주고 싶을 때 사용한다.
말그대로 다른 함수를 꾸며주는 함수를 의미한다.

app/decorator.py
from django.shortcuts import redirect

def login_required(function):
    # wrapping한 함수와 기존 함수의 인자값을 맞춰 줘야 한다
    def wrap(request, *args, **kwargs):
        user = request.session.get('user')
        if user is None or not user:
            return redirect('registration/login')
        return function(request, *args, **kwargs)

    return wrap
app/views.py
@method_decorator(login_required, 'get')
class LikeArticleView(RedirectView):
    def get_redirect_url(self, *args, **kwargs):
      #args는 튜플, kwargs는 사전형을 인자로 받음. 리디렉션할 대상 url를 구성
        return reverse('detail_themeRev', kwargs={'themeRev_pk':kwargs['pk']})
        #reverse(): view 함수를 사용하여 URL을 역으로 계산=url이 변경되어도 pattern name만 알면 됨
        #args와 kwargs를 동시에 전달할 수 없음

    def get(self, *args, **kwargs):
        username = self.request.session.get('user')
        user = get_object_or_404(User, userID = username)
        article = get_object_or_404(ThemeRev, pk=kwargs['pk'])
        #pk가 존재하는 themeRev 있으면 가져오고 아님 404에러발생

        if Like.objects.filter(user=user, article=article).exists():
          Like.objects.filter(user=user, article=article).delete()
          article.themeRevRecom -= 1
          article.save()
          # messages.add_message(self.request, messages.ERROR, '좋아요는 한번만 가능합니다.') 차후구현
          return HttpResponseRedirect(reverse('detail_themeRev', kwargs={'themeRev_pk':kwargs['pk']}))
        else:
          Like(user=user, article=article).save()
          #like 모델이 존재하면 (좋아요를 이미 눌렀으면 error 발생), 아니면 Like모델에 저장

        article.themeRevRecom += 1
        article.save()

        # messages.add_message(self.request, messages.SUCCESS, '좋아요가 반영되었습니다.') 차후구현

        return super(LikeArticleView, self).get(self.request, *args, **kwargs)
        #super()메서드가 자체적으로 response생성, 페이징 처리해주기 때문에 사용
        #super()로 부모클래스의 메서드를 쓸 수 있음. redirectview를 상속받아 사용

파이썬에서 제공해주는 데코레이터 기능을 이용해야 하는 이유

1.분석, 로깅, 인스트루먼테이션

규모가 큰 애플리케이션에서는 현재 무슨 일이 벌어지고 있는지 구체적으로 측정하고 다양한 활동을 정량화하는 지표를 기록해야 할 때가 많습니다. 데코레이터는 그러한 중요 이벤트를 전용 함수나 메서드에 캡슐화함으로써 이러한 요구사항을 아주 가독성 높고 손쉽게 처리할 수 있습니다.

2.유효성 검사와 런타임 검사

파이썬의 타입 체계는 타입에 엄격하지만(strongly typed) 매우 동적입니다. 이 모든 이점에도 불구하고 파이썬의 이런 특성 때문에 자바 같은 정적 타입 언어라면 컴파일 시점에서 포착했을 법한 버그가 생길 수 있습니다. 하지만 시야를 좀 더 넓히면 들어오고 나가는 데이터에 대해 좀 더 세련된 맞춤형 검사를 강제하고 싶을 수도 있습니다. 데코레이터를 이용하면 이 모든 작업을 손쉽게 처리하고 한번에 여러 함수에 적용할 수 있습니다.

이 외에도 파이썬의 문법 확장, 재사용 불가능한 코드의 재사용 등의 이점이 있다.(한빛 미디어 칼럼참조)

참조 블로그
[쿠키와 세션]
https://ssungkang.tistory.com/entry/Django%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9C%A0%EC%A7%80%ED%95%98%EA%B8%B0-%EC%BF%A0%ED%82%A4%EC%99%80-%EC%84%B8%EC%85%98
[데코레이터] https://nachwon.github.io/decorator/
[데코레이터를 작성해야 하는 이유] https://www.hanbit.co.kr/media/channel/view.html?cms_code=CMS5689111564

0개의 댓글

관련 채용 정보