Журнал LinuxFormat - перейти на главную

LXF91:Java EE

Материал из Linuxformat
Версия от 10:40, 4 августа 2009; Moradan (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Содержание

В прошлый раз вы, под чутким руководством Александра, разобрались с JSP и переписали "Телефонную книгу" с учетом этой технологии. Сегодня мы двинемся дальше и поговорим об авторизации, сессиях, фильтрах и использовании разделяемых объектов в web-приложениях.
ЧАСТЬ 3 Каждый линуксоид знает, что система уровня предприятия должна обеспечивать разграничение доступа. Антон Черноусов расскажет, что может предложить здесь Java EE.

Скачать исходный код примера

Процесс передачи информации

Давайте рассмотрим стандартную ситуацию: пользователь вызывает различные страницы одного web-приложения. Если бы этот "диалог" происходил по телефону, он был бы примерно таким:

  • Пользователь (набирает номер): Алло, это приложение?
  • Приложение: Да, это приложение.
  • (Пользователь кладет трубку)
  • Пользователь (набирает номер): Меня зовут Георг.
  • Приложение: Да, Георг, мы вас внимательно слушаем.
  • (Пользователь кладет трубку)
  • Пользователь (набирает номер): Дайте мне, пожалуйста, всю информацию.
  • Приложение: Возьмите...

Отметим, что после каждого обмена репликами пользователь кладет трубку, разрывая соединение - именно таким образом в большинстве случаев и происходит обмен данными между браузером пользователя и сервером, на котором работает web-приложение. Возникает резонный вопрос: как передавать информацию между соединениями, открываемые в рамках одной сессии? Для этого могут использоваться самые различные методы:

  • перезапись URL;
  • скрытые поля;
  • cookie;
  • сессионный объект.

Из всех представленных методов наиболее простым и в тоже время мощным является сессионный объект (Session), реализованный в Java посредством интерфейса javax.servlet.http.Session. Ранее, чтобы получить данные от пользователя или обеспечить возможность их транспортировки внутри приложения, мы использовали объект типа HttpServletRequest. Основное отличие HttpSession заключается во времени жизни, схематично изображенном на Рис.1. Объект типа HttpServletRequest предназначен для передачи запроса (и необходимых данных) от браузера к web-приложению и существует только между запросом и ответом, в то время как объект HttpSession обеспечивает средства для хранения и доступа к данным пользователя на протяжениии всего периода работы с приложением. )(Рис.1) Время жизни объектов HttpServletRequest и HttpSession. Следует, однако, иметь в виду, что отследить момент, когда пользователь перестает работать с приложением, не всегда возможно. Поэтому в настройках сервера устанавливается некоторое предельное время существования объекта HttpSession после получения последнего запроса.

Рис. 1

Время жизни объектов HttpServletRequest и HttpSession

Сессия

Объект session в JSP является предопределеным, то есть с ним можно сразу же начинать работать. Чтобы создать сессионный объект в сервлете, воспользуемся следующим методом:

HttpSession session = aRequest.getSession(true);

Здесь aRequest – это экземпляр объекта HttpServletRequest, то есть запрос переданный сервлету.

Классы, реализующие интерфейс HttpSession, имеют два замечательных метода: setAttribute(String, Object) и getAttribute(String). Метод setAttribute(String, Object) применяется для добавления объекта в сессию, а getAttribute(String) – для извлечения объекта из нее. Например:

 session.setAttribute(“sameKey”,sameObject);
 SameObject aObject = (SameObject) session.getAttribute(“sameKey”);

Фактически, объект session – это таблица Hashtable, в которой можно хранить любое количество пар типа «ключ – объект». При использовании сессионного объекта данные приложения не отправляются пользователю так, как это происходит с cookie. Сессионный объект хранится на сервере персонально для каждого клиента. Сервер различает сессии с помощью маркера, который передается пользователю. Маркер хранится в cookies браузера до конца сессии, что накладывает некоторые ограничения на клиентское рабочее место (использование cookies для вашего приложения должно быть разрешено). В этом можно легко убедиться, просмотрев сохраненные cookies в вашем любимом браузере.

Рис. 2

Маркер сессии. Герои романа «Лабиринт отражений» пытались повесить друг другу маркер под видом невинного поцелуя, а наше web-приложение действует еще хитрее и незаметнее.

Авторизация

Логично предположить, что чаще всего сессии применяются там, где необходимо авторизовать пользователя и предоставлять доступ к определенным функциям приложения только при наличии соответствующих привилегий. Давайте обратимся к нашей телефонной книге: ее просмотр разрешен всем желающим, а для добавления нового телефона, редактирования и удаления записи необходима авторизация.

Давайте создадим JSP-страницу auth.jsp, которая будет запрашивать у пользователя имя и пароль. Сохраните файл в каталоге jsps и введите в него следующий текст:

<span style=”color: green;><%=request.getAttribute(“message”)%>
</span>
<form action=<%=request.getContextPath()%>/auth” method=”post”>
        <table>
               <tr><td>Логин: </td><td><input type=”text” name=”login”/>
</td></tr>
               <tr><td>Пароль: </td><td>
                         <input type=”password” name=”password”/></td>
</tr>
               <tr><td colspan=2” align=”center”>
                        <input type=”submit” value=”Авторизоваться”/></td>
</tr>
           </table>
</form>

Легко видеть, что в форме имеется два поля ввода и одна кнопка. Заметим также, что поле для ввода пароля имеет тип password, так что вместо нажатых клавиш в окне браузера будут отображаться до боли знакомые «звездочки» (‘*’).

Теперь внесем изменения в AddressBookServlet: расширим ветвление в методе handle следующим образом:

 }else if (/auth”.equals(target)) {
           handleAuth(aRequest, aResponse);
       }

Дело за малым – осталось написать метод handleAuth(aRequest,aResponse), который и будет обрабатывать данные, введенные в форму, то есть производить авторизацию пользователя.

    private void handleAuth(HttpServletRequest aRequest,
               HttpServletResponse aResponse) throws IOException,
 ServletException {
       String login = aRequest.getParameter(“login”);
       String password = aRequest.getParameter(“password”);
       if ((login != null) && (password != null)) {
           // here is auth process
           if ((login.equals(“user”)) && (password.equals(“userPass”))) {
               //here is success auth
               HttpSession session = aRequest.getSession(true);
               session.setAttribute(“auth”,aRequest.getParameter(“login”));
               aRequest.setAttribute(“message”, null);
               outputPage(“index.jsp”, aRequest, aResponse);
           } else {
               //here is failed auth
               aRequest.setAttribute(“message”,
                            “Неверный логин или пароль, повторите ввод
 данных”);
               outputPage(“auth.jsp”, aRequest, aResponse);
           }
       } else {
           //here is no data to auth
           aRequest.setAttribute(“message”,
                        “Логин и пароль не могут быть пустыми, повторите
 ввод данных”);
           outputPage(“auth.jsp”, aRequest, aResponse);
      }
   }

Чтобы не усложнять приложение, мы используем простейший способ авторизации – имя пользователя и пароль жестко зашиты в теле метода. С точки зрения безопасности и масштабируемости лучше использовать для хранения этих данных БД.

В случае удачной авторизации мы помещаем имя пользователя в объект session и перенаправляем его на главную страницу. Если же авторизация не удалась (введено неверное имя пользователя и/или пароль), пользователю будет предложено повторить попытку.

Таким образом, если авторизация прошла успешно, в сессии пользователя будет сохранен объект, содержащий его имя. Используя этот факт, можно изменить index.jsp и спрятать ссылку на добавление нового телефона от посторонних глаз:

        <%String name = (String)session.getAttribute(“auth”);
         if (name != null){ %>
              <p>Вы авторизованны как: <%=name%> </p>
              <a href=<%=request.getContextPath()%>/add”>Добавить
 запись</a><br/>
         <%} else {%>
             <a href=<%=request.getContextPath()%>/auth”>
 Авторизоваться</a><br/>
         <%} %>

Мы приветствуем пользователя, используя значение атрибута auth и предоставляем ему доступ к функции добавления нового контакта. Чтобы спрятать удаление и редактирование записей от неавторизованного пользователя, необходимо внести аналогичные изменения в файл view.jsp.

Фильтры

Наше приложение, к сожалению, страдает проблемами безопасности: злонамеренный пользователь может обойти процедуру авторизации, обратившись к функциям редактирования/удаления напрямую, по адресу request.getContextPath() + действие (/add; /edit; /remove); можно также непосредственно вызывать JSP-страницы, расположенные в каталоге /jsps – нужно только узнать их имена.

Что же делать? Проверять, авторизован ли пользователь перед выполнением привилегированного действия? Это неудобно – если количество JSP будет расти, это заставит вас написать много строк однотипного кода, поддержание которых будет отнимать ваше драгоценное время. Мы пойдем другим путем и воспользуемся так называемыми фильтрами.

Схема работы фильтров представлена на Рис. 3. Если для адреса, на который отображен сервлет, осуществляется фильтрация, запрос будет передан сервлету только после того, как пройдет через каждый установленный фильтр.

Рис. 3

Схема прохождения запроса через фильтры.

На самом деле, фильтр представляет из себя обыкновенный Java- класс, реализующий интерфейс javax.servlet.Filter. Например:

 public class FirstFilter implements Filter {
   private FilterConfig filterConfig;
   public void init(FilterConfig filterConfig) throws ServletException {
       System.out.println(“Filter init”);
       this.filterConfig = filterConfig;
   }
   public void doFilter(ServletRequest aRequest, ServletResponse
 aResponse, FilterChain filterChain)
   throws IOException, ServletException {
       System.out.println(“Filter used”);
       filterChain.doFilter(aRequest, aResponse);
   }
   public void destroy() {
       System.out.println(“Filter dead”);
       this.filterConfig = null;
   }
 }

Реализовав всего три метода: init (инициализация фильтра в момент старта приложения), destroy (освобождение ресурсов перед завершением работы приложения) и doFilter (собственно метод, выполняющий фильтрацию), вы получаете класс, способный существенным образом повлиять на работу вашего приложения.

Обратите внимание, что в приведенном выше примере метод doFilter заканчивается вызовом метода doFilter(aRequest, aResponse) объекта filterChain – это обеспечивает вызов следующего фильтра в цепочке (естественно, если фильтров несколько). Если фильтров больше нет, то управление будет передано следующему ресурсу, например, сервлету.

Остановимся подробнее на методе init, а точнее на объекте, реализующем интерфейс FilterConfig. Этот объект имеет четыре замечательных метода:

  • getFilterName() – возвращает имя фильтра;
  • getInitParameterNames() – возвращает объект Enumeration, который содержит в своем теле имена параметров текущего фильтра;
  • getInitParameter(String) – возвращает значение параметра, имя которого было передано в качестве аргумента;
  • getServletContext() – возвращает объект ServletContext, о котором мы поговорим ниже.

Как вы уже, наверное, поняли, объект FilterConfig позволяет получить доступ к конфигурации фильтра, которая была задана при его объявлении в дескрипторе развертывания (web.xml).

Ограничение доступа

Итак, воспользуемся фильтрами для ограничения доступа к ресурсам нашего приложения. Прежде всего, запретим пользователю обращаться напрямую к JSP, и, при попытке запросить ресурс из каталога /jsps, заставим его перейти на индексную (первую) страницу. Для этого напишем простой фильтр AccessFilter. Метод doFilter будет выглядеть следующим образом:

   public void doFilter(ServletRequest aRequest,
           ServletResponse aResponse, FilterChain filterChain)
           throws IOException, ServletException {
              ((HttpServletResponse)aResponse).
            sendRedirect(((HttpServletRequest)aRequest).getContextPath());
   }

Благодаря методу sendRedirect, вместо ожидаемого ресурса неавторизованный пользователь увидит index.jsp. Чтобы фильтр заработал, его необходимо подключить в дескрипторе развертывания. Это делается при помощи следующих строк:

   <filter>
       <filter-name>AccessFilterName</filter-name>
       <filter-class>AccessFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>AccessFilterName</filter-name>
      <url-pattern>/jsps/*</url-pattern>
   </filter-mapping>

В секции filter мы объявляем, что имени фильтра AccessFilterName соответствует класс AccessFilter, а в секции filter-mapping указываем, на какие объекты распространяется зона действия фильтра. В данном случае фильтр работает по шаблону, то есть используется для всех адресов типа /jsps/*, где вместо звездочки может быть все, что угодно.

Теперь давайте создадим класс AuthFilter, который будет ограничивать доступ к некоторым действиям для неавторизованных пользователей. Метод doFilter будет выглядеть следующим образом:

   public void doFilter(ServletRequest aRequest,
          ServletResponse aResponse, FilterChain filterChain)
          throws IOException, ServletException {
      HttpSession session = ((HttpServletRequest) aRequest).
 getSession();
      String name = (String) session.getAttribute(“auth”);
      if (name != null) {
          filterChain.doFilter(aRequest, aResponse);
      } else {
          aRequest.setAttribute(“action”, “auth”);
          RequestDispatcher dispatcher = aRequest.getRequestDispatcher(/
 auth”);
          dispatcher.forward(aRequest, aResponse);
      }
   }

Если в текущей сессии не задан атрибут auth, пользователь будет отправлен на страничку авторизации. Добавим в дескриптор развертывания следующие строки:

   <filter>
      <filter-name>AuthEditFilter</filter-name>
      <filter-class>AuthFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>AuthEditFilter</filter-name>
      <url-pattern>/edit</url-pattern>
   </filter-mapping>
   <filter>
      <filter-name>AuthAddFilter</filter-name>
      <filter-class>AuthFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>AuthAddFilter</filter-name>
      <url-pattern>/add</url-pattern>
   </filter-mapping>
   <filter>
      <filter-name>AuthRemFilter</filter-name>
      <filter-class>AuthFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>AuthRemFilter</filter-name>
      <url-pattern>/remove</url-pattern>
   </filter-mapping>

Обратите внимание, что хотя наш фильтр реализован одним-единственным классом, он может быть доступен по нескольким именам (в нашем случае: AuthEditFilter, AuthAddFilter, AuthRemFilter) и работать для разных URL.

Чтобы закрыть тему развертывания фильтров, рассмотрим пример настройки параметров фильтра в дескрипторе:

      <init-param>
          <description>
              That is description
          </description>
          <param-name>
              sameParamName
          </param-name>
          <param-value>
              sameParamValue
          </param-value>
       </init-param>

Секцию init-param необходимо создавать отдельно для каждого параметра, при этом она должна быть размещена внутри секции filter. Обязательными являются подсекции param-name и param-value, которые задают название параметра и его значение, соответственно.

Доступ к общим объектам

Наше приложение уже вполне работоспособно, однако рано или поздно нам захочется расширить его функциональность, и, возможно, для этого потребуется уже не один сервлет, а несколько, причем все они будут обращаться к некому общему ресурсу (информации или объектам). Например, вы можете захотеть узнать, сколько пользователей в данный момент авторизовано в приложении или получить из двух несвязанных сервлетов доступ к информации, хранящейся в одном файле.

Подобно сессионным объектам, существует и контекстный объект, который обеспечивает доступ к общим ресурсам из разных мест приложения. Использование контекстного объекта очень похоже на использование сессионного, и получить его экземпляр в сервлете можно следующим образом:

 ServletContext sc = this.getServletContext();

Этот объект обладает следующими методами:

  • getAttribute(String) – получение общего объекта по ключу;
  • getAttributeNames() – получение списка ключей общих объектов;
  • setAttribute(String, Object) – добавление нового объекта и соответствующего ему ключа;
  • removeAttribute(String) – удаление объекта, соответствующего ключу, из списка общедоступных объектов;

Несмотря на то, что в этой статье контекстному объекту уделено немного внимания, он является одним из мощнейших инструментов для обеспечения работы web-приложения.

Вместо заключения

Сегодня мы поговорили о сессионных и контекстных объектах, узнали как создавать и использовать фильтры, рассмотрели простейший пример авторизации пользователя (конечно, надо сделать оговорку, что было представлено весьма небезопасное и не промышленное решение, хотя для простого офисного приложения его может быть и достаточно). Наконец, мы модифицировали наше web-приложение «Телефонная книга», применяя практически все изученные возможности.

В следующий раз мы рассмотрим паттерн MVC и его вариацию для создания web-приложений: Model2. LXF

Персональные инструменты
купить
подписаться
Яндекс.Метрика