I want to enforce https routing for the login page only of my application.
Is it possible to do so with Play! without the use of a front end http server?
I want to enforce https routing for the login page only of my application.
Is it possible to do so with Play! without the use of a front end http server?
You can use an @Before
interceptor to redirect every request, even if the user types http:// directly. Below is the code that I use (it works when running containerless play run
, or when running behind a front end such as on Heroku).
public class HttpsRequired extends Controller {
/** Called before every request to ensure that HTTPS is used. */
@Before
public static void redirectToHttps() {
//if it's not secure, but Heroku has already done the SSL processing then it might actually be secure after all
if (!request.secure && request.headers.get("x-forwarded-proto") != null) {
request.secure = request.headers.get("x-forwarded-proto").values.contains("https");
}
//redirect if it's not secure
if (!request.secure) {
String url = redirectHostHttps() + request.url;
System.out.println("Redirecting to secure: " + url);
redirect(url);
}
}
/** Renames the host to be https://, handles both Heroku and local testing. */
@Util
public static String redirectHostHttps() {
if (Play.id.equals("dev")) {
String[] pieces = request.host.split(":");
String httpsPort = (String) Play.configuration.get("https.port");
return "https://" + pieces[0] + ":" + httpsPort;
} else {
if (request.host.endsWith("domain.com")) {
return "https://secure.domain.com";
} else {
return "https://" + request.host;
}
}
}
}
Here is an example that works with Java Play 2.1.1 and Heroku.
public class ForceHttps extends Action<Controller> {
// heroku header
private static final String SSL_HEADER = "x-forwarded-proto";
@Override
public Result call(Context ctx) throws Throwable {
final Result result;
if (Play.isProd() && !isHttpsRequest(ctx.request())) {
result = redirect("https://" + ctx.request().host()
+ ctx.request().uri());
}
else {
// let request proceed
result = this.delegate.call(ctx);
}
return result;
}
private static boolean isHttpsRequest(Request request) {
// heroku passes header on
return request.getHeader(SSL_HEADER) != null
&& request.getHeader(SSL_HEADER)
.contains("https");
}
}
Then to any controllers you want to check for https, add @With(ForceHttps.class). Or if you want all controllers to check, then add a class HttpsController extends Controller and have all your classes extend HttpsController.
e.g.
@With(ForceHttps.class)
public class HttpsController extends Controller {
}
I think that you can check in the controller for request.secure == true and then redirect to https.
If your using AWS, you can terminate your HTTPS at the Load Balancer and use a filter to redirect HTTP connection to HTTPS.
AWS Conf:
443 (Load Balancer) ----------> 80 (Server)
80 (Load Balancer) ----------> 80 (Server)
The Filter:
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)
}
}
}
You should be able. Do the following:
This should work
It does not seem to be possible from the controller point of view. From the template Pere solution works but this only generates the https url from the template.
If the user accesses the login action by either typing manually or following a link to the http url, there does not seem to be a way to enforce/redirect to https.
The best way seems to have a front end proxy.