понедельник, 10 августа 2009 г.

Как правильно закрывать сессию. session.invalidate() - не достаточно

Описание проблемы:
Есть сайт, с такими условными частями:
1) login.jsp - в котором вводятся логин и пароль
2) dispatcher - сервлет, который обрабатывает данные формы из login.jsp, проверяя из БД логин и пароль и перенаправляет (или назад в login.jsp (если проверка неудачна) или в welcome.jsp)
3) welcome.jsp (или home.jsp), на этой странице приветствия, профайл пользователя, домашняя страница пользователя, или любая другая страница с конфиденциальными данными и ссылками.
Пользователь нажимает по ссылке "выйти", dispatcher закрывает сессию и перенаправляет на страницу регистрации login.jsp.
4) вспомогательный объект, в котором находятся дополнительные/вспомогательные данные сессии.

Задача:
Правильно сделать так, чтобы пользователь, нажав на кнопку браузера "Назад" ("Back") не смог просматривать конфиденциальные данные предыдущего пользователя. Браузер кеширует данные.

Обсуждение:
Все оказалось не так просто.
session.invalidate() - проблему не решил.
session.removeAttribute("myObject")
- тоже может воскреснуть.
session.removeParameter("login")
- не существует
запретить кеширование: с одной стороны на это надеяться нельзя, поскольку сервер не может полностью контролировать работу браузеров всех версий и под все платформы, это клиентская часть. С другой стороны, и это во многих случая не помогает.
Отменять кеширование можно либо программным способом:
response.setContentType("text/html");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Pragma","no-cache")
либо (по сути это одно и тоже, к тому же скриптлеты уже считаются плохой манерой):
META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
META HTTP-EQUIV="Expires" CONTENT="-1">

и даже response.
sendRedirect() вместо
RequestDispatcher rd =
this.getServletConfig()
.getServletContext()
.getRequestDispatcher("login.jsp");
rd.forward(request, response);
не помогает.

Все дело в том, что когда пользователь, нажал кнопку назад в welcome.jsp объект, то объект, в котором мы храним данные сессии практически потерян, НО если нажать кнопку обновить (F5), он снова восстанавливается, поскольку браузер все-равно сохранил параметры этой страницы (request.getParameter("login") все-еще срабатывает со старыми данными).
Дополнительной мерой защиты есть создание Ajax-запроса на сервер для проверки наличия объекта сессии, который хранит результаты регистрации.
Пример сервлета:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
request.setCharacterEncoding("UTF-8");
HttpSession session = request.getSession();
Helper helper = (Helper) session.getAttribute("helper");
PrintWriter out = response.getWriter();
if (helper.isLoged()) {
out.println("1"); // valid
} else {
out.println("0"); // invalid
}
}


Пример клиента (с помощью JQuery):
function validate(){
$.ajax({
url: "checkSession",
type: "POST",
data: {0: 0}, //any minimal data
error: function(){ alert("виникла помилка Ajax"); } ,
success: function (data){
if (data != 1){
location.href="login.jsp";
}
}
}); // ajax
}


И welcome.jsp вызываем эту функцию.

Такой метод хорош. Но все-таки можно успеть остановить перенаправление (мне это удалось сделать) и опять же обновить страницу...

Хорошим трюком есть использование токенов.
В сервлете создаем генерацию токенов:
public static int TOKEN ;
public static int getNextToken(){
TOKEN++;
return TOKEN;
}

на странице с формой логина создаем дополнительный скрытый параметр:
input type="hidden" name="token" value="<%= Dispatcher.getNextToken() %>"
и в том же (предыдущем) сервлете делаем проверку:
if (new Long(reaquest.getParameter("token")) {
forward(request, response, "/login.jsp");
}

И срабатывает! Дело в том, что когда пользователь нажал на кнопку "выйти", его перенаплавило на login.jsp, в котором счетчик TOKEN увеличился. Но в закешированной странице это значение осталось старым, поэтому оно будет меньше текущего


И еще дополнительным способом (наверное даже самым лучшим) это использовать в login.jsp AJAX, который будет проверять в базе логин и пароль и перенаправит (в случае успешной аутентификации) на нужную страницу, которая НЕ ХРАНИТ И НЕ КЕШИРУЕТ результаты. В таком случае, параметр ACTION вместо сервлета перенаправления нужно установить action="#"

Комментариев нет: