• 业务系统中基于角色的访问控制RBAC


    RBAC 基础知识

    业务系统权限控制的基本形式是基于角色的访问控制RBAC)。简化后,模型如下所示:

     

    • 主题(系统用户)有多个角色。
    • 一个角色由一组权限组成。
    • 权限由一组操作组成

    具体来说,让我们看一下Redmine中的一个例子。

    • 默认情况下,可以为用户分配“管理员”、“开发者”和“报告者”角色。
    • “报告者”角色具有“添加问题”权限。
    • 具有“添加问题”权限的用户可以“创建新问题”。

    该模型在 Redmine 中表示如下。

     

    Redmine 允许将单个用户分配给具有不同角色的多个项目,因此模型是将上面的 Member 实体夹在中间。通过少量应用 RBAC 原则,您可以以有组织的思维方式适应复杂的业务需求。
    我没有看到权限表,但它们在角色表的权限列中以逗号分隔。这就是J-Walk所谓的 SQL 反模式设计。我想这是Redmine插件端可以自由添加权限的借口。

    RBAC 看起来是一个非常合理的设计,但似乎很少有人真正看到这样设计的业务系统。相信一定有人对一个让组织=角色或角色=权限的设计有想法。此外,似乎很少有框架能够正确支持 RBAC。

    不针对 RBAC 进行设计时的问题

    组织=角色

    您可以想象为什么这不是一个好主意,组织经常变化。如果这是一个角色,每次都会需要大量的数据维护。客户经常通过组织名称向我们询问类似角色的要求,但请务必设计一个单独的角色并将其与组织映射。

    角色 = 特权

    没有滚动等效的模式。为用户分配权限可能会让人不知所措。由于我们还没有找到一组称为角色的业务权限,如果我们想要满足 C2 覆盖,我们将需要测试尽可能多的权限组合。

    特权=操作

    一个权限可以控制多个操作。如后面将要描述的,拥有一定的权限A不仅允许访问某个URL,而且它还可以用于不同的链接和按钮来到达那个URL。让我们将您想要控制权限的操作分组并将它们设计为权限。

    用Java实现

    Java 在 Servlet API 中也有一个角色机制,可以在业务应用程序中使用。但是...角色通常需要灵活的操作。另一个常见的要求是管理员希望能够创建和删除新的。然后,像 JSR250 和 Servlet API 一样,将角色名称附加到注解中,或者像 isUserInRole 这样将其作为参数传递,ロール名过于死板和不方便。根据 RBAC 的定义,自然能够传递“特权”。

    因此,我创建了一个基于 Ninjaframework 的应用程序示例,它使用 JSR250 注解并使用权限名称而不是角色名称作为要传递给它们的参数。

    示例说明

    作为初始数据,准备了三种角色,并赋予以下权限。

    权威
    行政人员读问题、写问题、管理用户
    开发商读问题,写问题
    来宾阅读问题

    登录时获取授权

    一个现实的设计是在登录时从用户的角色中获取权限,并将它们与用户信息一起存储。因此,会话作为登录信息保存的 DTO 实现如下。

    1. @Data
    2. @RequiredArgsConstructor
    3. public class UserPrincipal implements Principal {
    4. @NonNull
    5. private String name;
    6. @NonNull
    7. private Set permissions;
    8. }

    在登录时,这将转换为权限并提供给会话。

    1. User user = em
    2. .createQuery("SELECT u FROM User u LEFT JOIN FETCH u.roles r LEFT JOIN FETCH r.permissions p WHERE u.account = :account", User.class)
    3. .setParameter("account", account)
    4. .getSingleResult();
    5. session.put("account", account);
    6. Set<Permission> permissions = new HashSet<>();
    7. user.getRoles().stream()
    8. .map(Role::getPermissions)
    9. .forEach(permissionsInRole -> permissions.addAll(permissionsInRole));
    10. session.put("permissions", permissions.stream().map(Permission::getName)
    11. .collect(Collectors.joining(",")));

    Ninjaframework 会话存储在 cookie 中,因此应该只输入诸如 ↑ 之类的字符串(这就是不使用 Ninjaframework 的原因)。

    方法权限

    然后,在调度到控制器方法时,我们检查我们拥有的权限以及附加到操作方法的权限。

    从身份验证过滤器中的会话恢复 UserPrincipal。

    1. Session session = context.getSession();
    2. if (session.get("account") == null) {
    3. return Results.redirect("/login");
    4. }
    5. Set<String> permissions = new HashSet<>();
    6. if (session.get("permissions") != null) {
    7. permissions = ImmutableSet.copyOf(Splitter.on(',').split(session.get("permissions")));
    8. }
    9. context.setAttribute("principal", new UserPrincipal(session.get("account"), permissions));

    然后授权过滤器可以检查 UserPrincipal 是否有权访问该方法。假设需要的权限写在控制器方法中如下所示的JSR250注解中。

    1. @UnitOfWork
    2. @RolesAllowed("readIssue")
    3. public Result list(Context context) {
    4. EntityManager em = entityManagerProvider.get();
    5. List<Issue> issues = em
    6. .createQuery("SELECT i FROM Issue i", Issue.class)
    7. .getResultList();
    8. Map<String, Object> bindings = VariablesHelper.create(context);
    9. bindings.put("issues", issues);
    10. return Results.html().render(bindings);
    11. }
    12. @UnitOfWork
    13. @RolesAllowed("writeIssue")
    14. public Result newIssue(Context context) {
    15. return Results.html().render(VariablesHelper.create(context));
    16. }

    授权检查如下所示:

    1. Method method = context.getRoute().getControllerMethod();
    2. RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
    3. PermitAll permitAll = method.getAnnotation(PermitAll.class);
    4. UserPrincipal principal = (UserPrincipal) context.getAttribute("principal");
    5. if (permitAll != null ||
    6. (rolesAllowed != null && contains(principal.getPermissions(), rolesAllowed.value()))) {
    7. return filterChain.next(context);
    8. } else {
    9. return Results.forbidden().html().template("views/403forbidden.ftl.html");
    10. }

    现在您所要做的就是如上所述注释您的控制器方法。

    在视图层按权限分发

    这不是使用权限的唯一方法,但它也将用于区分菜单和按钮。您还可以检查您是否拥有来自 UserPrincipal 的必要权限,并在视图端单独发布它们。

    Ninjaframework 的默认视图是 Freemarker,所以你可以这样写:

    1. <a href="#" class="header item">
    2. RBAC Example
    3. </a>
    4. <#if principal??>
    5. <a href="/" class="item">Home</a>
    6. </#if>
    7. <#if principal?? && principal.permissions?seq_contains("readIssue")>
    8. <a href="/issues/" class="item">Issue</a>
    9. </#if>
    10. <#if principal?? && principal.permissions?seq_contains("manageUser")>
    11. <a href="/users/" class="item">Users</a>
    12. </#if>

    当您拥有“管理员”权限时

    当您没有“管理员”权限时

    我能够像这样整理出来。

    如何运行示例

    1. % git clone https://github.com/kawasima/rbac-example.git
    2. % cd rbac-example
    3. % mvn compile
    4. % mvn waitt:run

    您可以使用 .
    哦,这个叫waitt的插件是2015年最火的插件之一,不用Spring Boot或者IDE的付费功能,不用打包web应用就可以启动。请尝试一下。

    春季安全

    Spring security 允许您实现 RBAC。但是,由于篇幅限制,我将省略它。

    概括

    尽管权限控制是业务系统中最常见的需求,但很难找到任何可以清楚地解释如何设计它的东西,而现实是有很多系统被设计成难以测试和操作的想法。我们希望本文对您的设计有所帮助。

  • 相关阅读:
    java计算机毕业设计ssm美妆产品进销存管理系统
    阿拉伯糖偶联核苷酸,UDP-b-L-arabinopyranose disodium salt,UDP-β-L-Ara.2Na
    设计模式之模板模式
    RK3568-74HC595
    模型的保存加载、模型微调、GPU使用及Pytorch常见报错
    FPGA面试笔试一些基础概念题目
    数据库连接关闭工具类、Statement介绍、PreparedStatement介绍及区别
    四氧化三铁Fe3O4/二氧化硅SiO2/二氧化钛TIO2/氧化锌ZNO/二氧化锰MnO2/二硫化钨MoS2/二硒化钨WSe2/碲化钨WTe2纳米粒包载顺铂
    重保特辑|筑牢第一道防线,云防火墙攻防演练最佳实践
    Linux安装GitLab教程
  • 原文地址:https://blog.csdn.net/allway2/article/details/126977121