权限系统中user与role不应双向持有引用,应仅由user持roleids或通过中间表查询;权限应拆分为user、role、permission及关联表,校验走sql join或缓存set;禁用role内嵌权限列表与继承设计,权限变更需明确生效机制。

权限系统里 Role 和 User 该不该直接持有对方引用?
多数初学者会设计成 User 类里加一个 List<role></role>,Role 类里再加一个 List<user></user> —— 这会导致双向依赖、序列化异常、JPA 中的 LazyInitializationException,甚至循环 JSON 序列化失败。
实际项目中,应只保留单向关联:由 User 持有 roleIds(List<long></long>)或通过中间表查询,Role 不反向维护用户列表。权限校验时走服务层联合查询,而非对象图遍历。
-
User实体只存roleId或roleCode字符串,避免强类型耦合 - 角色数据变更(如重命名)时,仅需更新
Role表,不影响User表结构 - JPA 场景下,用
@ManyToMany配合独立关联表,禁用mappedBy双向映射
判断用户是否有某权限,为什么别直接查 Role.permissionList?
把权限字符串(如 "user:delete")塞进 Role 实体的 List<string></string> 字段,看似简单,但会带来硬编码扩散、无法动态启停权限、审计困难等问题。数据库里一旦角色权限变多,这个字段就变成不可维护的字符串拼接黑洞。
正确做法是拆分为三张表:user、role、permission,再加一张 role_permission 关联表。权限校验走 SQL JOIN 或缓存后的 Set<string></string> 查找。
立即学习“Java免费学习笔记(深入)”;
SELECT p.code FROM permission p JOIN role_permission rp ON p.id = rp.permission_id JOIN role r ON r.id = rp.role_id JOIN user_role ur ON r.id = ur.role_id WHERE ur.user_id = ?
- 权限码统一用常量类管理,例如
PermissionCode.USER_DELETE,避免散落字符串 - 运行时将用户所有权限加载进
ConcurrentHashMap<long set>></long>缓存,过期时间设为 5–10 分钟 - 不要在
Role实体里加getPermissions()方法返回实时 DB 查询结果——那是 N+1 查询陷阱
SecurityContextHolder.getContext().getAuthentication() 返回 null 怎么办?
这是 Spring Security 未生效的典型信号,不是代码写错了,而是配置漏了。常见原因包括:没加 @EnableWebSecurity、过滤器链没注册、Servlet 容器启动顺序错乱、或用了非标准 DispatcherServlet 名称。
先确认是否引入了 spring-boot-starter-security,再检查配置类是否被 @Configuration 标记且无 @ConditionalOnMissingBean 干扰。
- 手动测试:在 Controller 里打印
SecurityContextHolder.getContext(),如果为 null,说明请求根本没进 Spring Security 过滤器链 - 检查
WebSecurityConfigurerAdapter(旧版)或SecurityFilterChainbean 是否正确定义并生效 - 若用 WebFlux,必须用
ReactiveSecurityContextHolder,不能混用 Servlet 版 API
RBAC 模型要不要支持「权限继承」或「角色继承」?
绝大多数业务系统不需要。所谓“管理员角色继承运营角色权限”,本质是给管理员多分配一个角色,而不是让角色之间产生父子关系。强行加继承逻辑,会显著增加权限计算复杂度、审计难度和前端展示逻辑。
真正需要继承的场景极少,比如 SaaS 多租户中平台级角色对租户角色的默认覆盖。此时也建议用「权限模板」代替继承:预置一组 TemplateRole,创建新租户角色时一键复制权限项,而非运行时动态解析继承链。
- 角色间不建
parent_id字段,避免递归查询和环形引用风险 - 权限判断永远基于「用户当前拥有的所有角色所对应的全部权限」做并集运算
- 如果 UI 需要显示“该角色包含哪些基础权限”,用静态元数据描述,而非从父角色实时读取










