python, 파이썬

Django session 로그인 연장 기능(feat. ajax)

개발자 박태영 2023. 8. 10. 20:44
SMALL

현재 Django로 ERP 중에 있는데 고객이 로그인연장을 요구하였다.

그래서 세션을 통해 로그인을 연장해보려고 했다.

 

프로세스는 로그인을 하고 15분동안 세션이 유지가 되며 로그인연장 버튼을 누르지 않으면 자동으로 로그아웃되는 형식이다.(은행과 비슷하다)

 

* 참고로 로그인 기능은 이미 완료된 상태이다

 

 

 

웹페이지(모자이크 후)

위 사진의 오른쪽 상단의 시간연장 버튼을 누르면 시간이 15:00으로 초기화가 되야한다.

 

 

#settings.py

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
]

django의 settings.py에는 위와 같이 session MIDDLEWARE가 등록되어 있기에 Import 하여 세션기능을 사용할 수 있다.

 

# settings.py

# Session 설정
SESSION_COOKIE_AGE = 900  # 15분

하단에 session 만료기한을 설정해준다. 초단위로 계산되므로 900초면 15분이다

 

# views.py

from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required
from django.contrib import auth
from django.contrib.sessions.models import Session
from django.http import JsonResponse

...
## 로그아웃
@login_required
def logout(request):
    auth.logout(request)
    return redirect('account:login')

## 남은 세션 시간
@login_required
def remaining_session_time(request):
    session_key = request.session.session_key
    expire_date = Session.objects.get(pk=session_key).expire_date
    remaining_time = expire_date - timezone.now()
    return JsonResponse({'remaining_session_time':remaining_time.seconds})

## 세션 연장
@login_required
def extend_session(request):
    if request.method == 'GET':
        request.session.set_expiry(900)
        return JsonResponse({'result':'success'})
...

views.py에서 위와 같은 함수들을 구성했다.

 

logout 함수는 단순히 로그아웃 후 로그인 화면으로 돌아가는 함수이다.

remaining_session_time 함수는 남은 세션시간을 계산하여 JsonResponse로 return 해준다.

extend_session 함수는 세션시간 종료시간을 현재시간을 기준으로 900초로 다시 계산한다.

 

django_session 테이블

extend_session 함수가 사용가능한 이유는 DB에 위와 같이 django_session 테이블이 만들어지는데 로그인할때마다 한 행이 만들어지고 expire_date라고 세션 종료시간이 기록된다.

extend_session 함수를 통해 이 expire_date를 다시 갱신하는 것이다.

 

// session.js

/* START ajax csrf token */
function getCookie(name) {
  var cookieValue = null;
  if (document.cookie && document.cookie !== '') {
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
      var cookie = cookies[i].trim();
      // Does this cookie string begin with the name we want?
      if (cookie.substring(0, name.length + 1) === (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});
/* END ajax csrf token */

var time;

// 남은 세션시간
function remaining_session_time() {
  $.ajax({
    type: 'GET',
    url: '/remaining_session_time/',
    dataType: 'json'
  }).done(function (data) {
    time = data['remaining_session_time'];
  });
}

// 세션 타이머
function session_timer() {
  if (time > 0) {
    time--;
    var minute = Math.floor(time / 60);
    var second = Math.floor(time % 60);
    if (second < 10)
      second = "0" + second;
    $('#sesson_time').text(`${minute}:${second}`);
  } else {
    alert("세션이 만료되었습니다. 다시 로그인해주세요.");
    clearInterval(timer);
    window.location.href = "/logout/";
  }
}

remaining_session_time(); // 남은 세션 시간 할당
timer = setInterval(session_timer, 1000); // timer 시작

// 세션 연장
$('#extend_submit').bind('click', function () {
  $.ajax({
    type: "GET",
    url: "/extend_session/",
    dataType: "json",
  }).done(function (data) {
    clearInterval(timer); // timer 삭제
    remaining_session_time(); // 남은 세션 시간 할당 
    timer = setInterval(session_timer, 1000); // timer 재시작
  }).fail(function (xhr, status, errorThrown) {
    console.log("error");
  });
});

위 코드를 보면 주석도 달았지만 윗부분은 Django와 ajax를 사용할 때 필요한 csrftoken 설정 코드들이다.

 

remaining_session_time 함수는 세션종료까지 남은 시간을 가져오는 함수이다

ajax로 views.py의 remaining_session_time 함수를 호출하여 return 값을 time변수에 할당한다.

 

session_timer 함수는 time함수에 할당된 시간(초)을 분과 초로 나누어 아이디가 session_time인 요소에 표현시킨다.

 

맨 밑쪽 코드는 jquery로 작성했는데 아이디가 extend_submit이라는 버튼을 클릭하면 views.py의 extend_session함수를 호출하여 세션을 연장시킨다.

 

js에서만 남은 세션 시간이 0초가 된다면 로그아웃되게 했기에 누군가가 악의적으로 script를 끄고 접속하면 이 기능이 작동안할까봐 확인을 했는데 django에서도 expire_time을 갖고 있으므로 설정시간 이후에 동작을 한다면 로그아웃 후 로그인페이지로 redirect 된다


수정(2023.09.17)

해당 기능은 로그인하면은 메인페이지로 이동이 되고, 서버에 session.jsremaining_session_time함수를 통해 ajax로 세션시간을 바로 요청한다. 그러면 django에서는 views.pyremaining_session_time함수를 통해 ORM으로 DB에서 값을 가져와 반환하는 것이다.

 

session에 관한 정보는 로그인하면 자동으로 DB에 쌓이는데 기능이 조금씩 쌓이고 서버가 무거워짐으로써 어쩔때는  session.js remaining_session_time함수가 요청할때 DB에 session 정보가 쌓이지 않아 의도한대로 이루어지지 않았다.

 

그래서 time변수를 기준으로 session_timer함수의 분기문에서 재귀조건을 추가하였다

// session.js

function getCookie(name) {
  var cookieValue = null;
  if (document.cookie && document.cookie !== '') {
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
      var cookie = cookies[i].trim();
      // Does this cookie string begin with the name we want?
      if (cookie.substring(0, name.length + 1) === (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

var time = null;

function remaining_session_time() {
  $.ajax({
    type: 'GET',
    url: '/remaining_session_time/',
    dataType: 'json'
  }).done(function (data) {
    time = data['remaining_session_time'];
    console.log(time); // for testing
  });
}

function session_timer() {
  if (time > 0) {
    time--;
    var minute = Math.floor(time / 60);
    var second = Math.floor(time % 60);
    if (second < 10)
      second = "0" + second;
    $('#sesson_time').text(`${minute}:${second}`);
  } else if(time == null){
    remaining_session_time();
    session_timer();
  }else{
    alert('세션이 만료되었습니다. 다시 로그인해주세요.');
    clearInterval(timer);
    window.location.href = "/logout/";
  }
}

remaining_session_time();
timer = setInterval(session_timer, 1000);

$('#extend_submit').bind('click', function () {
  $.ajax({
    type: "GET",
    url: "/extend_session/",
    dataType: "json",
  }).done(function (data) {
    clearInterval(timer);
    remaining_session_time();
    timer = setInterval(session_timer, 1000);
  }).fail(function (xhr, status, errorThrown) {
    console.log("error");
  });
});

 

 

LIST