Using the security module

One of the basic functions of an application is the need for authentication and authorization. If you only have basic needs and checks, you can use the security module that is already bundled with Play. This recipe shows simple use cases for it.

You can find the source code of this example in the chapter3/secure directory.

Getting ready

Create an application and put the security module in the configuration. Though you do need to install the module, you do not need to specify a version because it is built in with the standard Play distribution. The conf/dependencies.yml entry looks like the following:

require:
    - play
    - play -> secure

As usual, nothing is complete without a test, here it goes:

public class SecurityTest extends FunctionalTest {

    @Test
    public void testThatIndexPageNeedsLogin() {
        Response response = GET("/");
        assertStatus(302, response);
        assertLocationRedirect("/login", response);
    }

    @Test
    public void testThatUserCanLogin() {
        loginAs("user");
        Response response = GET("/");
        assertContentMatch("Logged in as user", response);
    }

    @Test
    public void testThatUserCannotAccessAdminPage() {
        loginAs("user");
        Response response = GET("/admin");
        assertStatus(403, response);
    }

    @Test
    public void testThatAdminAccessAdminPage() {
        loginAs("admin");
        Response response = GET("/admin");
        assertStatus(302, response);
    }

    private void assertLocationRedirect(String location, Response resp) {
        assertHeaderEquals("Location", "http://localhost"+location, resp);    
    }

    private void loginAs(String user) {
        Response response = POST("/login?username=" + user + "&password=secret");
        assertStatus(302, response);
        assertLocationRedirect("/", response);
    }
}

These four tests should validate the application behavior. First, you cannot access a page without logging in. Second, after logging in as user you should be redirected to the login page. The login page should contain the username. The third test checks to make sure that the useruser may not get access to the admin page, while the fourth test verifies a valid access for the admin user.

This test assumes some things, which are laid down in the implementation:

  • A user with name user and password secret is a valid login
  • A user with name admin and password secret is a valid login and may see the admin page
  • Watching the admin page results in a redirect instead of really watching a page

You might be wondering why the Play server is running in port 9000, but there is no port specified in the location redirect. The request object is created by the tests with port 80, as default. The port number does not affect testing because any functional test calls the Java methods inside of the Play framework directly instead of connecting via HTTP to it.

How to do it...

Let's list the steps required to complete the task. The routes file needs to include a reference to the secure module:

*       /                module:secure
GET     /                Application.index
GET     /admin           Application.admin

Only one single template is used in this example. The template looks like this:

#{extends 'main.html' /}
#{set title:'Home' /}

<h1>Logged in as ${session.username}</h1>

<div>
Go to #{a @Application.index()}index#{/a}
<br>
Go to #{a @Application.admin()}admin#{/a}
</div>

#{a @Secure.logout()}Logout#{/a}

The Controller looks like the following:

@With(Secure.class)
public class Application extends Controller {

    public static void index() {
        render();
    }

    @Check("admin")
    public static void admin() {
        index();
    }
}

The last part is to create a class extending the Secure class. This class is used as class annotation in the controller. The task of this class is to implement a basic security check, which does not allow login with any user/pass combination like the standard implementation does:

public class SimpleSecurity extends Secure.Security {

    static boolean authenticate(String username, String password) {
        return "admin".equals(username) &&"secret".equals(password) ||
            "user".equals(username) &&"secret".equals(password);
    }    

    static boolean check(String profile) {
        if ("admin".equals(profile)) {
            return connected().equals("admin");
        }
        return false;
    }

    static void onAuthenticated() {
        Logger.info("Login by user %s", connected());
    }
    static void onDisconnect() {
        Logger.info("Logout by user %s", connected());
    }
    static void onCheckFailed(String profile) {
        Logger.warn("Failed auth for profile %s", profile);
        forbidden();
    }
}

How it works...

A lot of explanation is needed for the SimpleSecurity class. It is absolutely necessary to put this class into the controller package, otherwise no security checks will happen. The routes configuration puts the secure module in front of all other URLs. This means that every access will be checked, if the user is authenticated, with the exception of login and logout of course.

The template shows the logged-in user, and offers a link to the index and to the administration site as well as the possibility to log out.

The controller needs to have a @With annotation at class level. It is important here to refer to the Secure class and not to your own written SimpleSecure class, as this will not work at all.

Furthermore, the admin controller is equipped with a @Check annotation. This will make the secure module perform an extra check to decide whether the logged-in user has the needed credentials.

The most important part though is the SimpleSecure class, which inherits form Secure.Security. The authenticate() method executes the check where the user is allowed to log in. In the preceding example it only returns success (as in Boolean true) if the user logs in with username admin or user and password secret in both cases.

Furthermore, there are three methods which are executed only when certain events happen, in this case a successful login, a successful logout, and missing permissions even though the user is logged in. This last case can happen only when the Check annotation is used on a controller, like done in the admin() controller. Furthermore, the check() method in the SimpleSecure class needs to be defined. In this case the check method performs two checks. First, if the value inside of the check annotation was admin and the second if the currently logged-in user is admin as well. So this check resembles the most simple admin check that can be implemented.

There's more...

This module has been kept as simple as possible intentionally. Whenever you need more complex checks, this module might not be what you search for, and you should write something similar yourself or extend the module to fit your needs. For more complex needs, you should take a look at the deadbolt and secure permissions modules.

Declare only one security class

You should have only one class in your project which inherits from security. Due to the problem that the classloader possibly loads classes randomly, Play always picks the first it finds. There is no possibility to enforce which security class is used.

Implementing rights per controller with the secure module

In Chapter 2 there was an example where you could put certain rights via an annotation at a controller. Actually, it is not too hard to implement the same using the secure module. Take a few minutes and try to change the example in that way.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.18.101