Как заставить Play Framework 2 всегда использовать SSL?

У меня есть приложение Play Framework, работающее на Heroku, используя конечную точку SSL Heroku.

Я хочу сделать все страницы доступными только через SSL.

Какой лучший способ для этого?

До сих пор лучшим решением было использовать onRouteRequest в моих GlobalSettings и маршрутных non-SSL запросах для специального обработчика перенаправления:

override def onRouteRequest(request: RequestHeader): Option[Handler] = {
  if (Play.isProd && !request.headers.get("x-forwarded-proto").getOrElse("").contains("https")) {
    Some(controllers.Secure.redirect)
  } else {
    super.onRouteRequest(request)
  }
}

и

package controllers

import play.api.mvc._

object Secure extends Controller {

  def redirect = Action { implicit request =>
    MovedPermanently("https://" + request.host + request.uri)
  }
}

Есть ли способ сделать это полностью из GlobalSettings? Или что-то еще лучше?

Ответы

Ответ 1

Мы сделали так, как вы, но с фильтром воспроизведения, который генерирует MovedPermanently вместо метода контроллера.

Я не думаю, что есть лучший способ с героку, или, по крайней мере, мы не смогли найти какую-либо функцию для отключения незашифрованного HTTP.

Ответ 2

Вот еще один способ использования фильтра. Это также использует строгую безопасность транспорта, чтобы гарантировать, что будущие запросы перейдут на https.

object HTTPSRedirectFilter extends Filter with Logging {

    def apply(nextFilter: (RequestHeader) => Future[SimpleResult])(requestHeader: RequestHeader): Future[SimpleResult] = {
        //play uses lower case headers.
        requestHeader.headers.get("x-forwarded-proto") match {
            case Some(header) => {
                if ("https" == header) {
                    nextFilter(requestHeader).map { result =>
                        result.withHeaders(("Strict-Transport-Security", "max-age=31536000"))
                    }
                } else {
                    Future.successful(Results.Redirect("https://" + requestHeader.host + requestHeader.uri, 301))
                }
            }
            case None => nextFilter(requestHeader)
        }
    }
}

Ответ 3

Вот решение для Java-версии Play Framework.

Добавьте в файл Global.java следующее:

@Override
public Handler onRouteRequest(RequestHeader request) {
    String[] x = request.headers().get("X-Forwarded-Proto");
    if (Play.isProd() && (x == null || x.length == 0 || x[0] == null || !x[0].contains("https")))
        return controllers.Default.redirect("https://" + request.host() + request.uri());
    return super.onRouteRequest(request);
}

Ответ 4

В Play 2.5.x Java (я думаю, должен работать и в игре 2.4.x, но используя Promise.F вместо CompletionStage), я добавил фильтр:

public class TLSFilter extends Filter {
    @Inject
    public TLSFilter(Materializer mat) {
        super(mat);
    }

    @Override
    public CompletionStage<Result> apply(Function<Http.RequestHeader, CompletionStage<Result>> next, Http.RequestHeader rh) {
        if (Play.current().isProd()) {
            String[] httpsHeader = rh.headers().getOrDefault(Http.HeaderNames.X_FORWARDED_PROTO, new String[]{"http"});
            if (Strings.isNullOrEmpty(httpsHeader[0]) || httpsHeader[0].equalsIgnoreCase("http")) {
                return CompletableFuture.completedFuture(Results.movedPermanently("https://".concat(rh.host().concat(rh.uri()))));
            }
        }
        return next.apply(rh).toCompletableFuture();
    }
}

А затем добавьте его в список фильтров, чтобы использовать его:

public class AppFilters extends DefaultHttpFilters {

    @Inject
    public AppFilters(TLSFilter tlsFilter, GzipFilter gzipFilter) {
        super(tlsFilter, gzipFilter);
    }
}

И затем, чтобы использовать ваши фильтры, добавьте следующее внутри application.conf:

play.http.filters = "filters.AppFilters"

И обратите внимание, что если у вас включен обработчик запросов (найдите play.http.requestHandler внутри файла application.conf), фильтры не будут работать, я предлагаю обрабатывать запросы с использованием фильтров и удалять ваш текущий запросHandler.

Ответ 5

У меня было аналогичное требование, используя Play 2.2.3. Я запускаю свое приложение за балансировщиком нагрузки, которое делает завершение SSL, и хотел, чтобы все HTTP-запросы были перенаправлены на SSL. В моем случае мой балансировщик (Amazon ELB) добавляет заголовок X-Forwarded-Proto, поэтому я отключал его следующим образом. В Global.java(или какой-либо класс, который у вас есть, который расширяет GlobalSettings), добавьте этот метод:

@SuppressWarnings("rawtypes")
@Override
public Action onRequest(Request actionRequest, Method actionMethod) {

  String forwardedProtocol = actionRequest.getHeader("X-Forwarded-Proto");

  if (StringUtils.equalsIgnoreCase(forwardedProtocol, "http")) {

    // Redirect to HTTPS
    final String secureURL = "https://" + actionRequest.host() + actionRequest.uri();
    return new Action.Simple() {
      @Override
      public Promise<play.mvc.SimpleResult> call(Context ctx) throws Throwable {
        return Promise.pure(Results.movedPermanently(secureURL));
      }
    };

  } else {

    return super.onRequest(actionRequest, actionMethod);

  }
}

Это не обрабатывает пользовательские порты, поэтому некоторые реализации могут потребовать немного дополнительного кодирования.