AAA - ActFramework Security Framework II - Application

Keywords: Java git Database xml

stay Last article In this blog, we introduce the concept of AAA security framework. Next, we begin to describe how to use AAA security framework in a practical project (this blog will implement a multi-user Todo list system). In this blog, we will assume that the application uses MongoDB to store data. However, some code about the application of security framework is independent of the specific database.

1. Introducing project dependency

Add act-aaa plug-in dependencies to your pom.xml file:

    <dependency>
      <groupId>org.actframework</groupId>
      <artifactId>act-aaa</artifactId>
      <version>${act-aaa.version}</version>
    </dependency>

Among them, ${act-aaa.version} is the version of the act-aaa plug-in. The latest version is 1.0.2 by the time this article is written.

Note: The most direct change that occurs when your project adds dependencies is that all controller response methods require authentication. If you don't do any further work, all access will be returned to 401 Unauthorised response.

2. Processing Controller Method without Identity Authentication

act-aaa plug-in defaults that all HTTP access requires authentication. However, it is clear that any web application has access structures that do not require authentication. The simplest examples are login and registration.

If a response method does not require authentication, add the @org.osgl.aaa.NoAuthentication annotation to the method:

@NoAuthentication
public void loginForm() {}

If all response methods of a controller do not require authentication, you can add the @org.osgl.aaa.NoAuthentication annotation on the controller class:

@NoAuthentication
public class PublicAccessController {
    ...
}

3 Model class

3.1 Create a UserLinked interface

The UserLinked interface will be used to check whether a specific protected resource belongs to the currently accessing user:

package demo.security;

public interface UserLinked {
    /**
     * Return the user ID 
     */
    public String userId();
}

3.2 Create User Classes

Every application that needs authentication needs a Model class to model the user who is interacting with the system. Here is a basic User class code. Specific applications can add fields and logic they need.

package demo.model;

@Entity("user")
public class User extends MorphiaAdaptiveRecord<User> implements UserLinked {

    public String email;
    private String password;

    @Override
    public String userId() {
        return this.email;
    }

    public void setPassword(String password) {
        this.password = Act.crypto().passwordHash(password);
    }

    public boolean verifyPassword(char[] password) {
        return Act.crypto().verifyPassword(password, this.password);
    }

    public static class Dao extends MorphiaDao<User> {

        public User findByEmail(String email) {
            return findOneBy("email", email);
        }

    }

}

Note that we use the tools provided by ActFramework in the User class to hash and verify password plaintext. See more about this. This blog.

3.3 Creating TODO Classes

We implemented a multi-user TODO list management in this blog. Here is a simple TODO Model class:

package demo.model;

@Entity("todo")
public class Todo extends MorphiaAdaptiveRecord<Todo> implements UserLinked {

    /**
     * Store the owner's email
     */
    public String owner;

    @Override
    public String userId() {
        return this.owner;
    }

    public static class Dao extends MorphiaDao<Todo> {

        public Iterable<Todo> findByOwner(User owner) {
            return findBy("owner", owner.email);
        }

    }

}

4. Configuring the Security Layer

4.1 Define permissions

AAA's API supports the use of two permissions, the first is to pass in an instance of a class that implements org.osgl.aaa.Permission; the second is to pass in a string. In this blog we will use the second simple form. The following permissions are defined in this TODO application:

package demo.security;

public final class TodoPermission {

    private TodoPermission() {}

    public static final String PERM_CREATE_TODO_ITEM = "create-todo-item";
    public static final String PERM_UPDATE_TODO_ITEM = "update-todo-item";
    public static final String PERM_VIEW_TODO_ITEM = "view-todo-item";
    public static final String PERM_DELETE_TODO_ITEM = "delete-todo-item";

}

The TodoPermission class above is mainly for the convenience of using permission constants in applications. Next, we will define a configuration file to tell act-aaa the permissions used in the application and their characteristics. Add a file named acl.yaml under the src/main/resources/directory, as follows:

create-todo-item:
  type: permission
  dynamic: false

update-todo-item:
  type: permission
  dynamic: true

view-todo-item:
  type: permission
  dynamic: true

delete-todo-item:
  type: permission
  dynamic: true

Note that in the above definition, except for create-todo-item permissions, the dynamic attribute of all permissions is true, because only when create-todo-item needs to be verified, we do not need to verify the data (i.e. Todo instance, because it has not been created), while other permissions need to check whether the accessed data (i.e. Todo instance) belongs to the current user.

4.2 Configuration Application Security Framework

This step will use a class to set various configurations of act-aaa:

package demo.security;

/**
 * Here we use the generic parameter to tell act-aaa about the user model class
 */
public class TodoSecurity extends ActAAAService.Base<demo.model.User> {

    /**
     * In this simple Todo app every signed up user get granted 
     * all of the following permissions
     */
    private static final Set<String> DEFAULT_PERMS = C.set(
            PERM_CREATE_TODO_ITEM, 
            PERM_DELETE_TODO_ITEM, 
            PERM_UPDATE_TODO_ITEM,
            PERM_VIEW_TODO_ITEM
    );

    /**
     * We tell act-aaa `email` is the key to extract the user from database
     */
    @Override
    protected String userKey() {
        return "email";
    }

    /**
     * Just return the default permission set 
     */
    @Override
    protected Set<String> permissionsOf(User user) {
        return DEFAULT_PERMS;
    }

    /**
     * inject the logic of password verification into act-aaa
     */
    @Override
    protected boolean verifyPassword(User user, char[] password) {
        return user.verifyPassword(password);
    }

    /**
     * This will help to check a protected resource against the current logged in user
     * if the permission been authorised is dynamic
     */
    public static class DynamicPermissionChecker extends DynamicPermissionCheckHelperBase<UserLinked> {
        @Override
        public boolean isAssociated(UserLinked userLinked, Principal principal) {
            return S.eq(userLinked.userId(), principal.getName());
        }
    }

}

5. controller

5.1 Login Registration Controller

package demo.controller;

public class LoginController {
        @Inject
    private User.Dao userDao;

    @GetAction("/login")
    public void loginForm() {
    }

    @PostAction("/login")
    public void login(String email, String password, H.Flash flash, ActionContext context) {
        User user = userDao.authenticate(email, password);
        if (null == user) {
            flash.error("cannot find user by email and password combination");
            redirect("/login");
        }
        context.login(email);
        redirect("/");
    }

    @GetAction("/sign_up")
    public void signUpForm() {
    }

    @PostAction("/sign_up")
    public void signUp(User user, ActionContext context, @Provided PostOffice postOffice) {
        if (userDao.exists(user.email)) {
            context.flash().error("User already exists");
            redirect("/sign_up");
        }
        user.activated = false;
        userDao.save(user);
        postOffice.sendWelcomeLetter(user);
        redirect("/sign_up_ok");
    }

    @GetAction("/sign_up_ok")
    public void signUpConfirm() {
    }

    @GetAction("/activate")
    public void activate(String tk, ActionContext context) {
        Token token = Act.crypto().parseToken(tk);
        notFoundIfNot(token.isValid());
        User user = userDao.findByEmail(token.id());
        notFoundIfNull(user);
        context.session("tk", tk);
        render(user);
    }

    @PostAction("/activate")
    public void completeActivation(String password, ActionContext context) {
        String tk = context.session("tk");
        notFoundIfNull(tk);
        Token token = Act.crypto().parseToken(tk);
        notFoundIfNot(token.isValid());
        User user = userDao.findByEmail(token.id());
        token.consume();
        user.setPassword(password);
        user.activated = true;
        userDao.save(user);
        context.login(user.email);
        redirect("/");
    }

}

The controller mainly realizes the following functions:

  1. Land
  2. Register and send activation mail
  3. Response to Activated Link Request
  4. Processing intensification requests (initialization password)

5.2 Todo Controller

@Controller("/todo")
public class TodoController extends AuthenticatedController {

    @Inject
    private TodoItem.Dao dao;

    @GetAction
    public Iterable<TodoItem> myItems() {
        AAA.requirePermission(me, PERM_VIEW_TODO_ITEM);
        return dao.findBy("owner", me.email);
    }

    @PostAction
    public TodoItem add(String subject) {
        AAA.requirePermission(me, PERM_CREATE_TODO_ITEM);
        TodoItem todoItem = new TodoItem(subject);
        todoItem.owner = me.email;
        return dao.save(todoItem);
    }

    @PutAction("{id}")
    public TodoItem update(@DbBind("id") TodoItem todo, String subject) {
        notFoundIfNull(todo);
        AAA.requirePermission(todo, PERM_UPDATE_TODO_ITEM);
        todo.subject = subject;
        return dao.save(todo);
    }

    @DeleteAction("{id}")
    public void delete(@DbBind("id") TodoItem todo) {
        notFoundIfNull(todo);
        AAA.requirePermission(todo, PERM_DELETE_TODO_ITEM);
        dao.delete(todo);
    }

}

The controller provides RESTful services to operate TODO items, including:

  1. Get all TODO items from the current user
  2. Create a new TODO project
  3. Modify existing TODO projects
  4. Delete TODO items

All requests are executed by the authorized party.

5.3 AuthenticatedController

public abstract class AuthenticatedController {
    @LoginUser
    protected User me;
}

Providing the controller is a recommended usage. All controllers that require user login can inherit the controller and automatically obtain an instance of the current login user: this.me. This is done using the @LoginUser annotation provided by act-aaa and dependency injection by ActFramework.

summary

This blog describes how to use act-aaa plug-ins in applications, including:

  1. Introducing dependency
  2. Creating User Classes and Other User Associated Classes for Applications
  3. AAA Layer for Configuring Applications
  4. Processing user login and activating account number
  5. Authorization on Resource Controller Method

The project code of this blog is saved on the code cloud: https://git.oschina.net/greenlaw110/yatl

Reference resources

Posted by limke on Wed, 12 Dec 2018 21:06:06 -0800