Добрый день!
В одном проекте мне потребовалось сохранять контакты в 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