Easily Secure your Play 2.0 Application


This is my first post about Play Framework. It's quite an interesting framework because of its dissociative identity disorder between Java and Scala. I'm liking it actually, except for two key things:
  • The name (it's impossible to search stuff on the Net) 
  • The documentation (which brings us to this post) 
  • No IDE support for the Scala HTML stuff (at least in Eclipse Juno) 
Nevertheless, it has some interesting stuff built in it like its MVC model based on annotations, the routes file, database evolution and those good looking TODO and error pages.

Authentication on Play!

After having some forms ready I got to the point of authentication, and lucky me (and for this blog) there's nothing, nada, niet, in Play's documentation about this. But, when you download Play 2.0.x you get a folder with samples for Java and Scala which I recommend because it's very useful. In this case the Zentasks project has a good starting point for understanding authentication in Play.

You can start with the plain application generated by play, run it and test that you get that nice welcome page that Play generates.
Then generate these files:
  • Secured.java (auth) 
  • User.java (model) 
  • Authentication.java (controller) 
  • LoginForm.java (view) 
Here's all the code:


The User model (1-12) will be responsible of persisting our authentication data (email, password, etc) and it's a standard JPA annotated class

Note here that we are not extending from play.db.ebean.Model nor we are using Play constraint annotations because in this example I wanted to also show that it's possible to use standard JPA entities. This means that our models can live outside of Play in a separate JAR with no dependencies to any ORM so we can reuse our models from different applications with different ORMs (at the moment I was testing with using Ebean or Hibernate for our project) or even with plain JDBC.

The LoginForm (14-30) class is bind to our form in the login.scala.html view:



The validate() method (25) is important because Play uses Spring to validate the forms data. In this case the validation is being done by Play's Constraints annotations and we use the method to authenticate the user.

You could also implement the org.springframework.validation.Validator interface if that makes you feel more confident.

The login.scala.html is a simple HTML form with the binding to the LoginForm (full package name)

The Authentication controller (32-70) is the responsible of handling the actions associated to authentication: login (34) and logout (62); and provides the authentication method (53).

The Secured class (72-83) will handle authentication specific logic, like what to do with unauthenticated users.

This class is a good place to add groups or profiles specific methods like isOwner or isMember.

We then need to annotate the controllers we want to be protected and since we're starting with the bootstrapped application, we will secure the Application controller with the annotation @Security.Authenticated(Secured.class)(line: 85)

And finally, edit your routes file to reflect the new controller

# Home page
GET /        controllers.Application.index()

# Authentication
GET  /login  controllers.Authentication.login()
POST /login  controllers.Authentication.authenticate()
GET  /logout controllers.Authentication.logout()

Oh! And of course you have to configure your datasource to use the H2 database. Now restart the whole thing and when you go to http://localhost:9000/ you'll be redirected to http://localhost:9000/login and to logoff go to http://localhost:9000/logout

Common Models

One final note on having the models in a different JAR. For this example I created a maven project called common-models where all the persistence models and their tests are contained separately from the main Play application. To be able to add this project as a dependency to play, the Build.scala file should have something like:

val appDependencies = Seq(
  "com.play" % "common-models" % "0.1-SNAPSHOT",
)

val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings(
  resolvers += "Local Maven Repository" at "file:///"+Path.userHome.absolutePath+"/.m2/repository/"
)

And your application.conf ORM configuration should have something like:

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
db.default.user=sa
db.default.password=''
ebean.default="com.play.models.*"

In the common-models project I have the structure of the models as:

com.play.models.AbstractEntity
com.play.models.auth.User
com.play.models.auth.Profile
com.play.models.store.Location
com.play.models.store.Store

Just so you get the idea.

2 comentarios:

  1. This looks nice but I still have concerns about a real world app. Specifically regarding session expiration/cleanup. The JEE app server way of doing this is to provide a HttpSessionListener. For Play2 and cookie based sessions, it sounds like the best approach is to use the Play cache and its time to live parameter to do something equivalent.

    ResponderEliminar
  2. Yeah, actually, I'm starting of getting tired of Play's way of doing things and this session thing is one of them.

    ResponderEliminar