【SpringSecurity新手村系列】(5)RBAC角色权限与账户状态校验

简介: 本章在数据库认证基础上,通过 RBAC 三表(用户-角色-权限)把角色与权限从数据库接入 Spring Security,并补齐账户状态字段(过期、锁定、启用等),实现“可登录 + 可授权 + 可控状态”的完整认证闭环。

第五章 RBAC角色权限与账户状态校验

本章继续完善 Spring Security:把数据库中的用户、角色、权限真正接入认证流程,同时补齐账户状态字段(是否过期、是否锁定、凭证是否过期、是否启用),实现“可登录 + 可授权 + 可控状态”的完整认证授权闭环。

在前四章里,我们已经完成了表单登录、验证码、数据库查用户等基础能力。本章开始进入更贴近真实项目的一步:让权限从数据库驱动,而不是写死在代码里。


一、问题切入

只完成“用户名密码能登录”还不够,企业项目通常还需要三个层次:

  • 用户是否可登录(禁用、锁定、过期等状态)
  • 登录后具备哪些角色(如 ROLE_ADMINROLE_USER
  • 角色对应哪些细粒度权限(如 content:moderate

如果这三块没打通,系统会出现典型问题:

  • 用户状态失效:数据库标记禁用,但系统依然放行
  • 权限粗糙:所有登录用户权限一致
  • 运维成本高:每次改权限都要改代码

二、解决方案概览

本章的做法是:

  1. users 表存账号与四个状态字段(enabledaccount_non_expiredaccount_non_lockedcredentials_non_expired)。
  2. roles 表新增 role 字段,专门存角色编码(如 ROLE_ADMIN)。
  3. 通过 user_rolesrole_permissionspermissions 建立 RBAC 授权关系。
  4. UserServiceImpl#loadUserByUsername 中查询并组装 GrantedAuthority
  5. Users 实体实现 UserDetails,把数据库状态直接映射到认证判定。

三、数据库设计(RBAC + 账户状态)

3.1 账户状态字段

users 表中推荐保留以下字段:

  • enabled:账号是否启用
  • account_non_expired:账号是否未过期
  • account_non_locked:账号是否未锁定
  • credentials_non_expired:凭证(密码)是否未过期

MySQL 中通常用 TINYINT(1) 存储,Java 侧映射为 Boolean/boolean

3.2 RBAC 关系

  • roles:角色定义(包含展示名 name 与角色编码 role
  • permissions:权限点定义(如 perm_key=content:moderate
  • user_roles:用户-角色关联
  • role_permissions:角色-权限关联

推荐的数据语义是:

  • roles.name:角色展示名(如“管理员”)
  • roles.role:角色编码(如 ROLE_ADMIN,用于授权匹配)
  • permissions.perm_key:权限编码(如 content:moderate

roles.role 与 Spring Security 的约定保持一致,例如:

  • ROLE_ADMIN
  • ROLE_USER
  • ROLE_MODERATOR

3.3 一份最小可用的 RBAC 结构

roles(id, name, role, description)
permissions(id, perm_key, perm_name)
user_roles(id, user_id, role_id)
role_permissions(id, role_id, permission_id)

这样可以同时支持:

  • 按角色授权(hasRole
  • 按权限点授权(hasAuthority
  • 后台动态调整权限,无需重启服务

四、代码落地要点

4.1 Users 实现 UserDetails

Users 实体中实现 UserDetails 的四个状态方法,直接返回数据库字段:

@Override
public boolean isAccountNonExpired() {
   
    return this.accountNonExpired;
}

@Override
public boolean isAccountNonLocked() {
   
    return this.accountNonLocked;
}

@Override
public boolean isCredentialsNonExpired() {
   
    return this.credentialsNonExpired;
}

@Override
public boolean isEnabled() {
   
    return this.enabled;
}

Spring Security 在 AbstractUserDetailsAuthenticationProvider 中执行密码校验前,会依次调用这四个方法。只要其中一个返回 false,就立即抛出对应异常(如 DisabledExceptionLockedException 等),终止登录流程。因此,数据库中的状态字段会直接决定用户能否成功认证。

4.2 动态组装权限集合

UserServiceImpl 中,先查用户,再查角色码与权限码,并转换成 SimpleGrantedAuthority,最后回填到 Usersauthorities 字段:

List<String> roleCodes = usersMapper.selectRoleCodesByUserId(users.getId());
List<String> permissionKeys = usersMapper.selectPermissionKeysByUserId(users.getId());

List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (roleCodes != null) {
   
    authorities.addAll(roleCodes.stream().map(SimpleGrantedAuthority::new).toList());
}
if (permissionKeys != null) {
   
    authorities.addAll(permissionKeys.stream().map(SimpleGrantedAuthority::new).toList());
}
users.setAuthorities(authorities);

确保 Users 实体中有 private List<SimpleGrantedAuthority> authorities; 字段,并在 getAuthorities() 方法中返回它。

这样 UserDetailsService 返回的 Users 就携带了完整的角色和权限集合,供后续授权阶段使用。

这里有两个实践细节:

  1. 建议角色和权限都装入 authorities,避免“URL 用角色、方法用权限”时出现缺失。
  2. 角色建议固定 ROLE_ 前缀,权限点保持业务风格(如 module:action)。

4.3 Mapper 联表 SQL

角色查询:

<select id="selectRoleCodesByUserId" parameterType="java.lang.Long" resultType="java.lang.String">
  select r.role
  from roles r
  inner join user_roles ur on ur.role_id = r.id
  where ur.user_id = #{userId,jdbcType=BIGINT}
</select>

权限查询:

<select id="selectPermissionKeysByUserId" parameterType="java.lang.Long" resultType="java.lang.String">
  select distinct p.perm_key
  from permissions p
  inner join role_permissions rp on rp.permission_id = p.id
  inner join user_roles ur on ur.role_id = rp.role_id
  where ur.user_id = #{userId,jdbcType=BIGINT}
</select>

五、常见坑位与排查

5.1 Invalid bound statement (not found)

如果出现:

Invalid bound statement (not found): xxxMapper.xxxMethod

优先检查:

  • Mapper XML 的 namespace 是否和接口全限定名一致
  • XML 中 id 是否和接口方法名一致
  • mapper-locations 是否能扫描到对应 XML

5.2 Collection<? extends GrantedAuthority> 不能 add

错误示例:

Collection<? extends GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

应改为:

List<GrantedAuthority> authorities = new ArrayList<>();

List<SimpleGrantedAuthority>,再作为 Collection<? extends GrantedAuthority> 返回。

5.3 rolesList 为空导致 NPE

getAuthorities() 中如果依赖角色列表,务必判空,避免空指针。

5.4 账户状态字段为 null

Users 中四个状态字段如果是 Boolean 包装类型,数据库脏数据可能导致 null。建议:

  • 初始化数据时给默认值 1
  • 读取后做 Boolean.TRUE.equals(...) 风格的安全判定

5.5 只看到登录失败,不知道具体原因

账户状态失败通常对应异常:

  • DisabledException
  • LockedException
  • AccountExpiredException
  • CredentialsExpiredException

例如,AuthenticationFailureHandler 中可根据异常类型返回不同提示:

if (exception instanceof DisabledException) {
   
    response.getWriter().write("账号已被禁用");
} else if (exception instanceof LockedException) {
   
    response.getWriter().write("账号已被锁定");
}
// ... 其他异常同理

这部分将在统一异常处理章节中详细展开。


六、快速验收清单

可使用以下测试账号验证状态字段是否生效:

  • admin/admin123:应登录成功
  • disabled/disabled123:应被判定禁用
  • locked/locked123:应被判定锁定
  • expired/expired123:应被判定账号过期
  • credential_expired/credential123:应被判定凭证过期

七、核心概念总结

概念 说明
UserDetails Spring Security 的认证用户模型
GrantedAuthority 权限抽象,角色和权限点最终都可映射为它
RBAC 用户-角色-权限三层授权模型
roles.role 角色编码字段,建议使用 ROLE_ 前缀
四状态字段 enabled / accountNonExpired / accountNonLocked / credentialsNonExpired

八、总结

本章完成的核心升级:

  1. 建立了更规范的 RBAC 数据结构。
  2. 将账户状态字段真正接入 Spring Security 认证判定。
  3. 实现从数据库动态装载角色与权限集合。
  4. 梳理了 Mapper 绑定、泛型集合、空指针等高频问题。

到这里,你的项目已经不再是“演示级登录”,而是具备了真实系统常见的认证授权骨架。下一步可以继续做接口级授权规则(hasRole / hasAuthority)与异常返回统一化。


编辑者:Flittly
更新时间:2026年4月

目录
相关文章
|
6天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
2451 17
|
18天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
15914 47
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
23天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
34944 57
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
13天前
|
人工智能 JavaScript Ubuntu
低成本搭建AIP自动化写作系统:Hermes保姆级使用教程,长文和逐步实操贴图
我带着怀疑的态度,深度使用了几天,聚焦微信公众号AIP自动化写作场景,写出来的几篇文章,几乎没有什么修改,至少合乎我本人的意愿,而且排版风格,也越来越完善,同样是起码过得了我自己这一关。 这个其实OpenClaw早可以实现了,但是目前我觉得最大的区别是,Hermes会自主总结提炼,并更新你的写作技能。 相信就冲这一点,就值得一试。 这篇帖子主要就Hermes部署使用,作一个非常详细的介绍,几乎一步一贴图。 关于Hermes,无论你赞成哪种声音,我希望都是你自己动手行动过,发自内心的选择!
3050 29
|
2天前
|
云安全 人工智能 安全
|
3天前
|
人工智能 测试技术 API
阿里Qwen3.6-27B正式开源:网友直呼“太牛了”!
阿里云千问3.6系列重磅开源Qwen3.6-27B稠密大模型!官网:https://t.aliyun.com/U/JbblVp 仅270亿参数,编程能力媲美千亿模型,在SWE-bench等权威基准中表现卓越。支持多模态理解、本地部署及OpenClaw等智能体集成,已开放Hugging Face与ModelScope下载。
|
2天前
|
机器学习/深度学习 缓存 测试技术
DeepSeek-V4开源:百万上下文,Agent能力比肩顶级闭源模型
DeepSeek-V4正式开源!含V4-Pro(1.6T参数)与V4-Flash(284B参数)双版本,均支持百万token上下文。首创混合注意力架构,Agent能力、世界知识与推理性能全面领先开源模型,数学/代码评测比肩顶级闭源模型。
1312 6