前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AAA - ActFramework的安全框架II - 应用

AAA - ActFramework的安全框架II - 应用

作者头像
老码农
发布2018-06-27 10:32:59
7681
发布2018-06-27 10:32:59
举报
文章被收录于专栏:老码农专栏老码农专栏

上一篇博客中我们介绍了 AAA 安全框架的概念. 下面我们开始讲述实际项目中 (本篇博客将实现一个多用户的 Todo 列表系统) 如何使用 AAA 安全框架. 在本博客中我们将假设应用使用 MongoDB 来存储数据. 不过关于安全框架应用的部分代码和具体数据库无关.

1 引入项目依赖

在你的 pom.xml 文件中添加 act-aaa 插件依赖:

代码语言:javascript
复制
    <dependency>
      <groupId>org.actframework</groupId>
      <artifactId>act-aaa</artifactId>
      <version>${act-aaa.version}</version>
    </dependency>

其中 ${act-aaa.version} 是 act-aaa 插件的版本. 截止本文到落笔时的最新版本是 1.0.2.

**注意: ** 当你的项目加入依赖之后发生的最直接的变化是所有的控制器响应方法都需要身份认证了. 如果你没有进一步做任何工作, 所有的访问都会被返回 401 Unauthorised 响应.

2 处理不需要身份认证的控制器方法

act-aaa 插件默认所有的 HTTP 访问都需要身份认证. 但很明显任何 web 应用都有不需要身份认证的访问结构, 最简单的例子是登陆和注册.

如果某个响应方法不需要身份认证, 可以在该方法上添加 @org.osgl.aaa.NoAuthentication 注解:

代码语言:javascript
复制
@NoAuthentication
public void loginForm() {}

如果某个控制器所有的响应方法都不需要身份认证, 可以在控制器类上添加 @org.osgl.aaa.NoAuthentication 注解:

代码语言:javascript
复制
@NoAuthentication
public class PublicAccessController {
    ...
}

3 Model 类

3.1 创建一个 UserLinked 接口

UserLinked 接口将被用于检查具体某个被保护的资源是否属于当前正在访问的用户:

代码语言:javascript
复制
package demo.security;

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

3.2 创建 User 类

每个需要身份认证的应用都需要一个 Model 类来对正在和系统交互的用户建模. 下面是一个基本的 User 类代码. 具体应用可以增加自己需要的字段和逻辑

代码语言:javascript
复制
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);
        }

    }

}

注意 我们在 User 类中使用了 ActFramework 提供的工具来对密码明文进行哈希运算以及校验操作. 关于这方面更多的知识可以参见此篇博客.

3.3 创建 TODO 类

我们在本篇博客中实现一个多用户的 TODO 列表管理. 下面是一个简单的 TODO Model 类:

代码语言:javascript
复制
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. 配置安全层

4.1 定义权限

AAA 的 API 支持使用两种权限, 第一传入某个实现了 org.osgl.aaa.Permission 的类实例; 第二传入某个字符串. 在此篇博客中我们将使用第二种简单形式. 下面定义在这个 TODO 应用中涉及到的权限:

代码语言:javascript
复制
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";

}

上面的 TodoPermission 类主要是为了在应用中方便地使用权限常量. 下面我们将定义一个配置文件来告诉 act-aaa 应用中使用的权限及其特性. 在 src/main/resources/ 目录下添加一个名为 acl.yaml 的文件, 内容如下:

代码语言:javascript
复制
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

注意 在上面的定义中除了 create-todo-item 权限, 其他所有权限的 dynamic 属性均为 true, 这是因为只有当需要校验 create-todo-item 的时候我们不需要校验数据 (i.e. Todo 实例, 因为还没有创建), 而其他权限都需要检查被访问的数据 (i.e Todo 实例) 是否属于当前用户.

4.2 配置应用安全框架

此步骤将使用一个类来设置 act-aaa 的各种配置:

代码语言:javascript
复制
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. 控制器

5.1 登陆注册控制器

代码语言:javascript
复制
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("/");
    }

}

该控制器主要实现了下列功能:

  1. 登陆
  2. 注册并发送激活邮件
  3. 响应激活链接请求
  4. 处理激化请求(初始化密码)

5.2 Todo控制器

代码语言:javascript
复制
@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);
    }

}

该控制器提供操作 TODO 项的 RESTful 服务包括:

  1. 取当前用户所有的 TODO 项
  2. 创建新的 TODO 项目
  3. 修改已有的 TODO 项目
  4. 删除 TODO 项目

所有的请求均经过授权方予以执行

5.3 AuthenticatedController

代码语言:javascript
复制
public abstract class AuthenticatedController {
    @LoginUser
    protected User me;
}

提供该控制器是一个推荐用法, 所有需要用户登陆的控制器都可以继承该控制器, 并自动获取当前登陆用户的实例: this.me. 这是使用了 act-aaa 提供的 @LoginUser 注解, 并由 ActFramework 进行依赖注入的.

总结

本博客讲述了如何在应用中使用 act-aaa 插件, 包括:

  1. 引入依赖
  2. 创建应用的 User 类和其他 User 关联类
  3. 配置应用的 AAA 层
  4. 处理用户注册登陆以及激活帐号
  5. 在资源控制器方法上进行授权

本博客的项目代码保存在码云上: https://git.oschina.net/greenlaw110/yatl

参考

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 引入项目依赖
  • 2 处理不需要身份认证的控制器方法
  • 3 Model 类
    • 3.1 创建一个 UserLinked 接口
      • 3.2 创建 User 类
        • 3.3 创建 TODO 类
        • 4. 配置安全层
          • 4.1 定义权限
            • 4.2 配置应用安全框架
            • 5. 控制器
              • 5.1 登陆注册控制器
                • 5.2 Todo控制器
                  • 5.3 AuthenticatedController
                  • 总结
                  • 参考
                  相关产品与服务
                  对象存储
                  对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档