如何强制Play框架2始终使用SSL?

时间:2013-10-02 21:28:45

标签: scala ssl heroku playframework-2.0

我使用Heroku的SSL端点在Heroku上运行Play Framework应用程序。

我想仅通过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内完全执行此操作?还是更好的东西?

5 个答案:

答案 0 :(得分:12)

这是使用过滤器的另一种方式。这也使用严格的传输安全性来确保将来的请求转到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)
        }
    }
}

答案 1 :(得分:4)

我们已经像你一样做了很多,但是使用了一个生成MovedPermanently而不是控制器方法的播放过滤器。

我认为heroku没有更好的方法,或者至少我们找不到任何功能来禁用未加密的HTTP。

答案 2 :(得分:2)

以下是Play版Java版本的解决方案。

将以下内容添加到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);
}

答案 3 :(得分:2)

在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),过滤器将无效,我建议使用过滤器处理请求并删除当前的requestHandler。

答案 4 :(得分:0)

我使用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);

  }
}

这不会处理自定义端口,因此某些实现可能需要一些额外的编码。