среда, 19 августа 2009 г.

Запуск JSTL в Eclipse Dynamic Web Project

Оказывается не все так просто, если запускать Tomcat под Eclipse.
Если в Eclipse создать Dynamic Web Project, то простое добавление
jstl.jar и standard.jar в WEB_INF/lib может не помочь,
даже если в Java Build Path они есть.
При этом, если экпортировать .war, то в Tomcat запустится нормально.
Не запускается именно из-под Eclipse.

Помогло следующее:
при вызове контекстного меню в Run As -> Run configuration
выбирает нужный сервер из списка Apache Tomcat (перед этим нужно остановить)
справа выбираем сервер и в закладке Classpath и добавляем (Add jars):
jstl.jar, standard.jsr, servlet-api.jar, jsp-api.jar, el-api.jar

вторник, 18 августа 2009 г.

Hibernate: ошибка связанная с SLF4J

Проблема:
При запуске нового проекта на Hibernate 3.3.2 скопированы с архива (папка lib) все необходимые библиотеки, в том числе и slf4j-api-1.5.8.jar.
Но при старте теста появляется ошибка:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Exception in thread "main" java.lang.ExceptionInInitializerError

Решение:
На сайте http://www.slf4j.org/codes.html#StaticLoggerBinder эту ошибку описывают так:
"This error is reported when the org.slf4j.impl.StaticLoggerBinder class could not be loaded into memory. This happens when no appropriate SLF4J binding could be found on the class path. Placing one (and only one) of slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem."
То есть нужно в пути к библиотекам добавить один из (и только один slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar)

Скачать необходимую библиотеку можно с сайта http://www.slf4j.org/dist/ (берем ту же версию, что и slf4j-api).
В Jar архиве есть необходимые библиотеки.
Я взял slf4j-jdk14-1.5.8.jar.

понедельник, 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="#"