当前位置: 首页 > news >正文

Open Liberty使用指南及微服务开发示例(二)

续上篇

七、实现动态权限分配

目前,我们的系统基于 角色(Role) 进行权限控制,但角色权限是固定的。
现在,我们要实现
用户可动态分配权限(而不是仅靠角色)
每个用户可以拥有不同的权限集(CRUD 操作可灵活授权)
管理员可管理用户权限


方案设计

💡 采用基于权限的访问控制(PBAC - Permission-Based Access Control)

  • 用户拥有多个权限(READ, CREATE, UPDATE, DELETE)
  • 管理员可以动态调整用户权限

1、数据模型

定义 Permission 实体

@Entity
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private PermissionType type;

    @ManyToMany(mappedBy = "permissions")
    private List<Account> accounts;
}

 定义 PermissionType 枚举

public enum PermissionType {
    READ, CREATE, UPDATE, DELETE
}

修改 Account,增加权限字段

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;

    @Enumerated(EnumType.STRING)
    private Role role; // 仍保留角色,但权限可单独管理

    @ManyToMany
    @JoinTable(name = "account_permissions",
            joinColumns = @JoinColumn(name = "account_id"),
            inverseJoinColumns = @JoinColumn(name = "permission_id"))
    private List<Permission> permissions;
}

2、动态权限管理 API

获取用户权限

@Path("/permissions")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class PermissionResource {
    
    @PersistenceContext
    private EntityManager em;

    @GET
    @Path("/{userId}")
    public List<PermissionType> getUserPermissions(@PathParam("userId") Long userId) {
        Account account = em.find(Account.class, userId);
        return account.getPermissions().stream().map(Permission::getType).collect(Collectors.toList());
    }
}

 管理员给用户分配权限

@POST
@Path("/{userId}/assign")
public Response assignPermission(@PathParam("userId") Long userId, PermissionType permissionType) {
    Account account = em.find(Account.class, userId);
    Permission permission = em.createQuery("SELECT p FROM Permission p WHERE p.type = :type", Permission.class)
                              .setParameter("type", permissionType)
                              .getSingleResult();

    account.getPermissions().add(permission);
    em.merge(account);

    return Response.ok().build();
}

移除用户权限

@DELETE
@Path("/{userId}/remove/{permissionType}")
public Response removePermission(@PathParam("userId") Long userId, @PathParam("permissionType") PermissionType permissionType) {
    Account account = em.find(Account.class, userId);
    account.getPermissions().removeIf(p -> p.getType() == permissionType);
    em.merge(account);
    return Response.ok().build();
}

3、修改权限拦截器

📌 检查用户权限,而不仅仅是角色

@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @PersistenceContext
    private EntityManager em;

    @Context
    private SecurityContext securityContext;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        String username = securityContext.getUserPrincipal().getName();
        Account account = em.createQuery("SELECT a FROM Account a WHERE a.username = :username", Account.class)
                            .setParameter("username", username)
                            .getSingleResult();

        PermissionType requiredPermission = getRequiredPermission(requestContext.getMethod());

        boolean hasPermission = account.getPermissions().stream()
                                       .anyMatch(p -> p.getType() == requiredPermission);

        if (!hasPermission) {
            requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access Denied").build());
        }
    }

    private PermissionType getRequiredPermission(String method) {
        switch (method) {
            case "POST": return PermissionType.CREATE;
            case "PUT": return PermissionType.UPDATE;
            case "DELETE": return PermissionType.DELETE;
            default: return PermissionType.READ;
        }
    }
}

 4、前端 UI - 动态权限管理

1️⃣ 获取用户权限

📌 前端调用 API

export const getUserPermissions = async (userId) => {
    const token = localStorage.getItem("token");
    return axios.get(`http://localhost:8000/api/permissions/${userId}`, {
        headers: { Authorization: `Bearer ${token}` }
    });
};

2️⃣ 管理员分配权限

📌 前端调用 API

export const assignPermission = async (userId, permissionType) => {
    const token = localStorage.getItem("token");
    return axios.post(`http://localhost:8000/api/permissions/${userId}/assign`, { permissionType }, {
        headers: { Authorization: `Bearer ${token}` }
    });
};

3️⃣ UI 角色管理界面

📌 创建 Permissions.js

import { useEffect, useState } from "react";
import { getUserPermissions, assignPermission } from "./api";

export default function Permissions({ userId }) {
    const [permissions, setPermissions] = useState([]);

    useEffect(() => {
        getUserPermissions(userId).then(response => setPermissions(response.data));
    }, [userId]);

    const handleAssign = (permissionType) => {
        assignPermission(userId, permissionType).then(() => {
            setPermissions([...permissions, permissionType]);
        });
    };

    return (
        <div>
            <h2>User Permissions</h2>
            <ul>
                {permissions.map(perm => <li key={perm}>{perm}</li>)}
            </ul>
            <button onClick={() => handleAssign("CREATE")}>Grant Create</button>
            <button onClick={() => handleAssign("UPDATE")}>Grant Update</button>
            <button onClick={() => handleAssign("DELETE")}>Grant Delete</button>
        </div>
    );
}

 4️⃣ 在 App.js 中添加权限管理路由

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Permissions from "./Permissions";

export default function App() {
    return (
        <Router>
            <Routes>
                <Route path="/permissions/:userId" element={<Permissions />} />
            </Routes>
        </Router>
    );
}

📌 管理员可以访问 http://localhost:5173/permissions/1 来管理用户权限

总结

用户权限可动态管理
管理员可随时授予/撤销权限
前端 UI 自动适配不同权限

八、 实现用户组(User Group)功能

当前系统已经支持 动态权限分配,但每个用户需要单独管理权限,不够高效。
现在,我们要实现
用户可以加入多个用户组(每个组有一套权限)
用户自动继承用户组权限
管理员可以动态管理用户组

方案设计

💡 采用基于用户组(User Group)的权限管理

  • 用户组(UserGroup):一组用户共享相同的权限(如 "人事部"、"财务部")。
  • 用户(Account):可以加入多个组,并自动继承组的权限。
  • 权限继承
    • 用户权限 = 个人权限 + 所属用户组权限
    • 组权限变更时,所有成员的权限自动更新

1、数据模型

定义 UserGroup 实体

@Entity
public class UserGroup {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(name = "group_permissions",
            joinColumns = @JoinColumn(name = "group_id"),
            inverseJoinColumns = @JoinColumn(name = "permission_id"))
    private List<Permission> permissions;

    @ManyToMany(mappedBy = "groups")
    private List<Account> members;
}

修改 Account,增加用户组关联

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;

    @ManyToMany
    @JoinTable(name = "account_groups",
            joinColumns = @JoinColumn(name = "account_id"),
            inverseJoinColumns = @JoinColumn(name = "group_id"))
    private List<UserGroup> groups;

    @ManyToMany
    @JoinTable(name = "account_permissions",
            joinColumns = @JoinColumn(name = "account_id"),
            inverseJoinColumns = @JoinColumn(name = "permission_id"))
    private List<Permission> permissions;
}

2. 动态用户组管理 API

创建用户组

@Path("/groups")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserGroupResource {

    @PersistenceContext
    private EntityManager em;

    @POST
    public Response createGroup(UserGroup group) {
        em.persist(group);
        return Response.status(Response.Status.CREATED).entity(group).build();
    }
}

给用户组分配权限

@POST
@Path("/{groupId}/assign")
public Response assignPermission(@PathParam("groupId") Long groupId, PermissionType permissionType) {
    UserGroup group = em.find(UserGroup.class, groupId);
    Permission permission = em.createQuery("SELECT p FROM Permission p WHERE p.type = :type", Permission.class)
                              .setParameter("type", permissionType)
                              .getSingleResult();

    group.getPermissions().add(permission);
    em.merge(group);

    return Response.ok().build();
}

添加用户到用户组

@POST
@Path("/{groupId}/addUser/{userId}")
public Response addUserToGroup(@PathParam("groupId") Long groupId, @PathParam("userId") Long userId) {
    UserGroup group = em.find(UserGroup.class, groupId);
    Account user = em.find(Account.class, userId);

    group.getMembers().add(user);
    user.getGroups().add(group);

    em.merge(group);
    em.merge(user);

    return Response.ok().build();
}

移除用户组中的用户

@DELETE
@Path("/{groupId}/removeUser/{userId}")
public Response removeUserFromGroup(@PathParam("groupId") Long groupId, @PathParam("userId") Long userId) {
    UserGroup group = em.find(UserGroup.class, groupId);
    Account user = em.find(Account.class, userId);

    group.getMembers().remove(user);
    user.getGroups().remove(group);

    em.merge(group);
    em.merge(user);

    return Response.ok().build();
}

3、修改权限拦截器

📌 用户权限 = 个人权限 + 所属用户组的权限

@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @PersistenceContext
    private EntityManager em;

    @Context
    private SecurityContext securityContext;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        String username = securityContext.getUserPrincipal().getName();
        Account account = em.createQuery("SELECT a FROM Account a WHERE a.username = :username", Account.class)
                            .setParameter("username", username)
                            .getSingleResult();

        PermissionType requiredPermission = getRequiredPermission(requestContext.getMethod());

        boolean hasPermission = account.getPermissions().stream()
                                       .anyMatch(p -> p.getType() == requiredPermission)
                            || account.getGroups().stream()
                                      .flatMap(g -> g.getPermissions().stream())
                                      .anyMatch(p -> p.getType() == requiredPermission);

        if (!hasPermission) {
            requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access Denied").build());
        }
    }

    private PermissionType getRequiredPermission(String method) {
        switch (method) {
            case "POST": return PermissionType.CREATE;
            case "PUT": return PermissionType.UPDATE;
            case "DELETE": return PermissionType.DELETE;
            default: return PermissionType.READ;
        }
    }
}

4、前端 UI

1️⃣ 获取用户组

export const getUserGroups = async () => {
    const token = localStorage.getItem("token");
    return axios.get(`http://localhost:8000/api/groups`, {
        headers: { Authorization: `Bearer ${token}` }
    });
};

 2️⃣ 添加用户到组

export const addUserToGroup = async (groupId, userId) => {
    const token = localStorage.getItem("token");
    return axios.post(`http://localhost:8000/api/groups/${groupId}/addUser/${userId}`, {}, {
        headers: { Authorization: `Bearer ${token}` }
    });
};

3️⃣ UI - 用户组管理界面

📌 创建 UserGroups.js

import { useEffect, useState } from "react";
import { getUserGroups, addUserToGroup } from "./api";

export default function UserGroups({ userId }) {
    const [groups, setGroups] = useState([]);

    useEffect(() => {
        getUserGroups().then(response => setGroups(response.data));
    }, []);

    const handleAddUser = (groupId) => {
        addUserToGroup(groupId, userId).then(() => alert("User added!"));
    };

    return (
        <div>
            <h2>User Groups</h2>
            <ul>
                {groups.map(group => (
                    <li key={group.id}>
                        {group.name} 
                        <button onClick={() => handleAddUser(group.id)}>Add User</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

总结

用户组管理(创建/删除)
组权限管理(动态修改)
用户继承组权限
前端 UI 适配

九、实现多租户(Multi-Tenancy)支持

目前,我们的系统支持 用户组(User Group)动态权限分配,但所有用户共享同一数据库数据。
现在,我们要实现
每个组织(Organization)拥有自己的独立数据
用户只能访问自己所属组织的数据
管理员可以管理自己组织的用户,但不能管理其他组织
支持多租户架构(Schema 分离 / 数据库分离 / 逻辑分离)

方案设计

💡 多租户支持的三种方案

  1. 独立数据库(Database-per-Tenant):每个组织拥有自己的数据库
  2. 共享数据库,独立 Schema(Schema-per-Tenant):每个组织使用不同的 Schema
  3. 共享数据库,逻辑分离(Discriminator Column):同一表使用 tenant_id 进行数据隔离

选择方案: ✅ 方式 3(逻辑分离) 是最佳方案,适用于 SaaS 平台,管理成本低,数据隔离性强。

1、数据模型

修改 Organization 增加租户标识

@Entity
public class Organization {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    
    @OneToMany(mappedBy = "organization")
    private List<Account> accounts;
}

修改 Account 关联 Organization

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;

    @ManyToOne
    @JoinColumn(name = "organization_id")
    private Organization organization;
}

2、数据访问限制

📌 修改 EntityManager,强制过滤 tenant_id

@Provider
public class TenantFilter implements ContainerRequestFilter {

    @PersistenceContext
    private EntityManager em;

    @Context
    private SecurityContext securityContext;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        String username = securityContext.getUserPrincipal().getName();
        Account account = em.createQuery("SELECT a FROM Account a WHERE a.username = :username", Account.class)
                            .setParameter("username", username)
                            .getSingleResult();

        Long tenantId = account.getOrganization().getId();
        requestContext.setProperty("tenant_id", tenantId);
    }
}

3、多租户数据查询

📌 修改 OrganizationResource,确保查询数据时只返回当前租户的数据

@Path("/organizations")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class OrganizationResource {

    @PersistenceContext
    private EntityManager em;

    @Context
    private SecurityContext securityContext;

    @GET
    public List<Organization> getOrganizations() {
        Long tenantId = (Long) securityContext.getProperty("tenant_id");
        return em.createQuery("SELECT o FROM Organization o WHERE o.id = :tenantId", Organization.class)
                 .setParameter("tenantId", tenantId)
                 .getResultList();
    }
}

4、限制用户只能管理自己组织的账户

📌 修改 AccountResource

@Path("/accounts")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AccountResource {

    @PersistenceContext
    private EntityManager em;

    @Context
    private SecurityContext securityContext;

    @GET
    public List<Account> getAccounts() {
        Long tenantId = (Long) securityContext.getProperty("tenant_id");
        return em.createQuery("SELECT a FROM Account a WHERE a.organization.id = :tenantId", Account.class)
                 .setParameter("tenantId", tenantId)
                 .getResultList();
    }

    @POST
    public Response createAccount(Account account) {
        Long tenantId = (Long) securityContext.getProperty("tenant_id");
        account.setOrganization(em.find(Organization.class, tenantId));
        em.persist(account);
        return Response.status(Response.Status.CREATED).entity(account).build();
    }
}

5、前端 UI 适配

1️⃣ 获取当前租户信息

export const getTenantOrganizations = async () => {
    const token = localStorage.getItem("token");
    return axios.get(`http://localhost:8000/api/organizations`, {
        headers: { Authorization: `Bearer ${token}` }
    });
};

 2️⃣ 组织管理员只能查看本组织用户

export const getTenantUsers = async () => {
    const token = localStorage.getItem("token");
    return axios.get(`http://localhost:8000/api/accounts`, {
        headers: { Authorization: `Bearer ${token}` }
    });
};

3️⃣ UI - 限制不同租户的数据可见性

📌 修改 Organizations.js

import { useEffect, useState } from "react";
import { getTenantOrganizations } from "./api";

export default function Organizations() {
    const [organizations, setOrganizations] = useState([]);

    useEffect(() => {
        getTenantOrganizations().then(response => setOrganizations(response.data));
    }, []);

    return (
        <div>
            <h2>Organizations</h2>
            <ul>
                {organizations.map(org => (
                    <li key={org.id}>{org.name}</li>
                ))}
            </ul>
        </div>
    );
}

📌 修改 Users.js

import { useEffect, useState } from "react";
import { getTenantUsers } from "./api";

export default function Users() {
    const [users, setUsers] = useState([]);

    useEffect(() => {
        getTenantUsers().then(response => setUsers(response.data));
    }, []);

    return (
        <div>
            <h2>Users</h2>
            <ul>
                {users.map(user => (
                    <li key={user.id}>{user.username}</li>
                ))}
            </ul>
        </div>
    );
}

6、支持超级管理员(Super Admin)

📌 如果用户是 SUPER_ADMIN,可以管理所有租户

@Path("/organizations")
@GET
public List<Organization> getOrganizations() {
    String role = securityContext.getUserPrincipal().getName();
    if ("SUPER_ADMIN".equals(role)) {
        return em.createQuery("SELECT o FROM Organization o", Organization.class).getResultList();
    }
    
    Long tenantId = (Long) securityContext.getProperty("tenant_id");
    return em.createQuery("SELECT o FROM Organization o WHERE o.id = :tenantId", Organization.class)
             .setParameter("tenantId", tenantId)
             .getResultList();
}

总结

每个组织(Organization)数据独立
用户只能访问自己组织的数据
管理员只能管理自己组织的用户
超级管理员可管理所有组织

 未完,待续

相关文章:

  • DataBase【MySQL基础夯实使用说明(下)】
  • 在软件产品从开发到上线过程中,不同阶段可能出现哪些问题,导致软件最终出现线上bug
  • 网络安全之探险
  • Python Pandas(7):Pandas 数据清洗
  • Log4j定制JSON格式日志输出
  • 常用共轭先验分布
  • AWTK-WEB 快速入门(4) - JS Http 应用程序
  • Redis7——基础篇(一)
  • Jenkins 安装插件 二
  • MySQL - 索引 - 介绍
  • 如何下载Qt和运行第一个程序。
  • 【Elasticsearch】字符过滤器Character Filters
  • 51单片机之引脚图(详解)
  • postgresql源码学习(59)—— 磁盘管理器 SMGR
  • C#快速排序QuickSort将递归算法修改为堆栈Stack非递归方式
  • vue+springboot+webtrc+websocket实现双人音视频通话会议
  • Redisson介绍和入门使用
  • 二十六、使用docsify搭建文档管理平台
  • Docker 镜像推送到远程仓库
  • 2021年全国研究生数学建模竞赛华为杯E题信号干扰下的超宽带(UWB)精确定位问题求解全过程文档及程序
  • 习近平会见斯洛伐克总理菲佐
  • 【社论】以法治力量促进民企长远健康发展
  • 马上评|比余华与史铁生的友情更动人的是什么
  • 菲护卫艇企图侵闯中国黄岩岛领海,南部战区:依法依规跟踪监视、警告驱离
  • 咖啡戏剧节举办第五年,上生新所“无店不咖啡,空间皆可戏”
  • 60岁济南石化设计院党总支书记、应急管理专家李有臣病逝