我使用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
内完全执行此操作?还是更好的东西?
答案 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);
}
}
这不会处理自定义端口,因此某些实现可能需要一些额外的编码。