Django session 로그인 연장 기능(feat. ajax)
현재 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초로 다시 계산한다.
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.js의 remaining_session_time함수를 통해 ajax로 세션시간을 바로 요청한다. 그러면 django에서는 views.py의 remaining_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");
});
});