Добрый день!
В одном проекте мне потребовалось сохранять контакты в 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 для ответа, чтобы получить значение редиректа. В результате реализации данной идеи получился такой фильтр (часть методов опущена для упрощения примера):
В методе doFilter мы оборачиваем ответ сервлета в декоратор — анонимный класс, наследующий от HttpServletResponseWrapper. В этом классе мы переопределяем метод sendRedirect таким образом, чтобы переданный URL сохранялся в локальной переменной метода doFilter. Переменная loc, используемая в замыкании, должна быть обязательно объявлена как final, но при этом оставаться модифицируемой — это достигается за счет определения ее не как String, а как StringBuffer.
Для непосредственного редиректа из фрейма в главное окно используется JSP:
В одном проекте мне потребовалось сохранять контакты в 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>

