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 分离 / 数据库分离 / 逻辑分离)
方案设计
💡 多租户支持的三种方案:
- 独立数据库(Database-per-Tenant):每个组织拥有自己的数据库
- 共享数据库,独立 Schema(Schema-per-Tenant):每个组织使用不同的 Schema
- 共享数据库,逻辑分离(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)数据独立
✅ 用户只能访问自己组织的数据
✅ 管理员只能管理自己组织的用户
✅ 超级管理员可管理所有组织
未完,待续