Использование фильтра сервлетов для «всплытия» страницы из фрейма

в 9:35, , рубрики: decorator, filter, Google API, google app engine, java, JSP, Servlet, метки: , , , , ,

Добрый день!

В одном проекте мне потребовалось сохранять контакты в Google Contacts. Это несложно — надо только авторизоваться через OAuth в Google и получить ключ доступа. Но дело в том, что при этом делается переход на сайт Google, где собственно происходит авторизация и подтверждение доступа приложения к контактным данным. Я же предполагал делать работу с контактом в iframe, а в целях предотвращения clickjacking'а Google не позволяет этого делать. Стало быть, требуется как-то сделать, чтобы страница OAuth открывалась в главном окне, а не во фрейме. Мой вариант решения — под катом.

Для начала я попробовал сделать отдельное всплывающее окно — но оно регулярно блокировалось браузером, и я решил от этой идеи отказаться. Следующей идеей было сделать некое «всплытие» страницы OAuth из фрейма в главное окно.

В моем проекте серверная часть написана на Java для Google App Engine, а для работы с Google Data используется Google API 4 Java. В версии 1.7 появились два класса — AbstractAppEngineAuthorizationCodeServlet и AbstractAppEngineAuthorizationCodeCallbackServlet. Эти классы берут на себя «внутреннюю кухню» авторизации через OAuth, оставляя разработчику только создание логики взаимодействия с сервисами Google уже после авторизации. К сожалению, в них жестко зашит редирект на страницу авторизации, а переписывать эти классы самому мне не хотелось. Пришлось прибегнуть к системе фильтров.

Фильтры — это механизм, позволяющий выполнять заданные действия до или после формирования страницы сервлетом. В моем случае фильтр должен обнаружить произошедший в процессе работы сервлета редирект (на страницу OAuth) и выдать браузеру страницу, в котором средствами Javascript переадресованный URL будет открыт не во фрейме, а в главном окне. Первой идеей было искать в заголовках ответа тег Location, извлекать его значение и делать JS-переадресацию по нему. Но получение значений заголовков ответа появилось только в Servlets 3.0 (J2EE 6), а Google Apps Engine (или плагин GAE для Netbeans) использует только J2EE5. Пришлось придумывать другие способы.

На просторах интернета мне попалась интересная идея — использовать паттерн Decorator для ответа, чтобы получить значение редиректа. В результате реализации данной идеи получился такой фильтр (часть методов опущена для упрощения примера):

public class Frame2Window implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {
        try {
                final StringBuffer loc = new StringBuffer(); 
                loc.setLength(0);
                HttpServletResponseWrapper wrappedResp = new HttpServletResponseWrapper((HttpServletResponse)response) {
                        @Override
                        public void sendRedirect(String location) throws IOException{
                            loc.setLength(0);
                            loc.append(location);
                        }
                };
                chain.doFilter(request, wrappedResp);
                if(loc.length() > 0) {
                    HttpServletRequest req = (HttpServletRequest) request;
                    req.setAttribute("newUrl", loc.toString());
                    request.getRequestDispatcher("/pages/util/frame2window.jsp").include(request, response);
            }
        } catch (Throwable t) {
         // обработка исключений опущена, чтобы не загромождать код примера
        }
    }
}

В методе doFilter мы оборачиваем ответ сервлета в декоратор — анонимный класс, наследующий от HttpServletResponseWrapper. В этом классе мы переопределяем метод sendRedirect таким образом, чтобы переданный URL сохранялся в локальной переменной метода doFilter. Переменная loc, используемая в замыкании, должна быть обязательно объявлена как final, но при этом оставаться модифицируемой — это достигается за счет определения ее не как String, а как StringBuffer.

Для непосредственного редиректа из фрейма в главное окно используется JSP:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JS Redirect</title>
    </head>
    <script type="text/javascript">
     (function() {   
       if(window != window.top)
           window.top.location = "<%= request.getAttribute("newUrl") %>";
      })(); 
    </script>
    <body>
    </body>
</html>

Автор: gorynych_zmey

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js