Springboot继承Shiro
Springboot中的Shiro框架
首先了解下原理,了解下shrio的认证的逻辑,再讲解下springboot中,如何通过代码进行认证,授权操作。
Shiro框架的逻辑
RBAC模型
在讲解认证授权之前,先介绍下RBAC模型,Shiro框架后续用上的最后本质上,还是通过查询这个库。
-
定义:RBAC(Role-Based Access Control)即基于角色的访问控制模型,核心是通过 “用户 - 角色 - 权限” 的层级关系实现访问控制,简化权限管理流程。
-
核心要素
:
- 用户(User):系统的实际使用者,可被分配多个角色。
- 角色(Role):一组相关权限的集合,代表用户的职能或职位(如管理员、普通用户)。
- 权限(Permission):对系统资源的操作许可(如查看、编辑、删除等),可关联到角色。
- 关系:用户与角色、角色与权限均为多对多关系。
如何做到不同的角色有不同的权限。
sys_menu(权限 / 菜单表)
存储系统中所有可操作的权限(目录、菜单、按钮),对应 “创建文档”“查看文档” 等具体权限。
| id | parent_id | title | path | perms | component | type | created | sort_order | icon | status | updated |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 文档管理 | /document | NULL | Layout | 0 | 2024-01-01 00:00:00 | 1 | folder | 0 | 2024-01-01 00:00:00 |
| 2 | 1 | 文档列表 | /document/list | document:list | DocumentList | 1 | 2024-01-01 00:00:00 | 2 | list | 0 | 2024-01-01 00:00:00 |
| 3 | 2 | 创建文档 | NULL | document:create | NULL | 2 | 2024-01-01 00:00:00 | 3 | plus | 0 | 2024-01-01 00:00:00 |
| 4 | 2 | 查看文档 | NULL | document:view | NULL | 2 | 2024-01-01 00:00:00 | 4 | eye | 0 | 2024-01-01 00:00:00 |
| 5 | 2 | 编辑文档 | NULL | document:edit | NULL | 2 | 2024-01-01 00:00:00 | 5 | edit | 0 | 2024-01-01 00:00:00 |
| 6 | 2 | 删除文档 | NULL | document:delete | NULL | 2 | 2024-01-01 00:00:00 | 6 | trash | 0 | 2024-01-01 00:00:00 |
| 7 | 0 | 系统管理 | /system | NULL | Layout | 0 | 2024-01-01 00:00:00 | 7 | setting | 0 | 2024-01-01 00:00:00 |
| 8 | 7 | 用户管理 | /system/user | user:manage | UserManage | 1 | 2024-01-01 00:00:00 | 8 | user | 0 | 2024-01-01 00:00:00 |
解释:
-
type字段区分权限类型:0 = 目录(如 “文档管理”“系统管理”)、1 = 菜单(如 “文档列表”“用户管理”)、2 = 按钮(如 “创建文档”“删除文档”)。 -
perms字段对应具体操作权限,与前文的 P1-P5 对应关系:
- P1(创建文档)→
document:create - P2(查看文档)→
document:view - P3(编辑文档)→
document:edit - P4(删除文档)→
document:delete - P5(管理用户)→
user:manage
- P1(创建文档)→
sys_role(角色表)
存储系统中的角色,对应 “Admin、Manager、Employee、Guest”。
| id | name | code | remark | created | updated |
|---|---|---|---|---|---|
| 1 | 超级管理员 | ROLE_ADMIN | 拥有系统所有权限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
| 2 | 部门经理 | ROLE_MANAGER | 拥有文档全操作权限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
| 3 | 普通员工 | ROLE_EMPLOYEE | 拥有文档创建 / 查看 / 编辑权限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
| 4 | 访客 | ROLE_GUEST | 仅拥有文档查看权限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
解释:
code字段为角色标识,用于权限校验(如ROLE_ADMIN对应管理员)。- 角色权限范围与前文一致:Admin > Manager > Employee > Guest。
sys_role_menu(角色 - 权限关联表)
关联角色与权限,定义每个角色可操作的具体权限。
| id | role_id | menu_id | |
|---|---|---|---|
| 1 | 1 | 3 | (Admin 拥有 “创建文档” 权限) |
| 2 | 1 | 4 | (Admin 拥有 “查看文档” 权限) |
| 3 | 1 | 5 | (Admin 拥有 “编辑文档” 权限) |
| 4 | 1 | 6 | (Admin 拥有 “删除文档” 权限) |
| 5 | 1 | 8 | (Admin 拥有 “用户管理” 权限) |
| 6 | 2 | 3 | (Manager 拥有 “创建文档” 权限) |
| 7 | 2 | 4 | (Manager 拥有 “查看文档” 权限) |
| 8 | 2 | 5 | (Manager 拥有 “编辑文档” 权限) |
| 9 | 2 | 6 | (Manager 拥有 “删除文档” 权限) |
| 10 | 3 | 3 | (Employee 拥有 “创建文档” 权限) |
| 11 | 3 | 4 | (Employee 拥有 “查看文档” 权限) |
| 12 | 3 | 5 | (Employee 拥有 “编辑文档” 权限) |
| 13 | 4 | 4 | (Guest 仅拥有 “查看文档” 权限) |
解释:
- 角色权限严格遵循前文规则:Admin 拥有所有权限(P1-P5),Manager 缺少 “管理用户”(P5),Employee 缺少 “删除文档”(P4)和 “管理用户”(P5),Guest 仅保留 “查看文档”(P2)。
sys_user(用户表)
存储系统用户信息,对应 “张三、李四、王五、访客 001”。
| id | username | password | avatar | phone | created | updated | last_login | status | is_delete | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | zhangsan | $2a$10$xxxxxx(加密后) | /avatar/zhangsan.jpg | zhangsan@example.com | 13800138000 | 2024-01-01 00:00:00 | NULL | 2024-07-16 09:00:00 | 0 | 0 |
| 2 | lisi | $2a$10$xxxxxx(加密后) | /avatar/lisi.jpg | lisi@example.com | 13900139000 | 2024-01-02 00:00:00 | NULL | 2024-07-16 09:30:00 | 0 | 0 |
| 3 | wangwu | $2a$10$xxxxxx(加密后) | /avatar/wangwu.jpg | wangwu@example.com | 13700137000 | 2024-01-03 00:00:00 | NULL | 2024-07-16 10:00:00 | 0 | 0 |
| 4 | guest001 | $2a$10$xxxxxx(加密后) | /avatar/guest.jpg | guest001@example.com | NULL | 2024-07-16 08:00:00 | NULL | 2024-07-16 08:30:00 | 0 | 0 |
解释:
password字段存储加密后的密码(如 BCrypt 加密),避免明文泄露。status=0表示用户正常,is_delete=0表示未删除(逻辑删除标记)。last_login记录最近登录时间,用于追踪用户活动。
sys_user_role(用户 - 角色关联表)
关联用户与角色,定义每个用户所属的角色。
| id | user_id | role_id | |
|---|---|---|---|
| 1 | 1 | 1 | (张三→超级管理员) |
| 2 | 2 | 2 | (李四→部门经理) |
| 3 | 3 | 3 | (王五→普通员工) |
| 4 | 4 | 4 | (访客 001→访客) |
解释:
- 直接对应前文的用户 - 角色关系,通过
user_id和role_id关联,实现 “用户→角色→权限” 的间接映射。
Shiro框架
Shiro 的功能可概括为四大基石及相关支持特性:
四大核心功能
- Authentication(认证):验证用户身份(如用户名/密码登录、SSO登录)
- Authorization(授权):细粒度的权限控制(如"user:delete"权限校验)
- Session Management(会话管理):用户特定的会话管理,支持非 Web/EJB 环境
- Cryptography(加密):提供易于使用的加密算法
支持特性
- Web支持:提供URL拦截、Remember Me等Web专属功能
- 并发控制:支持多线程环境下的安全访问
- 缓存机制:提升权限验证性能(如EhCache集成)
- “记住我”:基于Cookie的持久化身份会话
- “运行方式”:允许特权用户临时扮演其他身份(Impersonation)
Shiro 架构组件
Shiro 架构主要包含三个核心概念:
-
Subject:当前用户(可指人、第三方服务等任何与软件交互的实体)
-
SecurityManager:管理所有 Subject,是 Shiro 架构的核心
-
Realm 是 Shiro 的核心安全数据访问对象(Security DAO),它:
- 封装了与安全数据源(数据库、LDAP等)的连接细节
- 提供统一的API供 SecurityManager 调用
- 负责将数据源的原始数据转换为 Shiro 可识别的安全信息
类比理解:就像JDBC连接数据库,Realm是Shiro连接各种安全数据源的标准接口。这里不好解释,就是类似存放了相关用户数据。通过一些列操作之后得到相关的用户的数据。
下面有关realm不了解可以先跳过,博主也暂时能力有限,没有找到更好的表述方式,可以暂时先跳过。跟着流程表述就行,这里还是比较晦涩难懂。写到这里博主觉得,可以先跟着后面的springboot是如何是如何使用shrio框架的进行过一遍流程。有不懂得地方再进行查阅,先弄懂相关的认证、授权的逻辑。先过一遍,后面再慢慢的一点的弄懂。先用着,再慢慢的了解其特性。
Realm 的基本概念
Realm(域)是 Shiro 框架中连接应用与安全数据源的 “桥梁” 或 “连接器”。当进行认证(登录)和授权(访问控制)验证时,Shiro 会通过 Realm 获取用户及其权限信息。
其核心特点包括:
- 是 Shiro 的安全数据访问层,相当于安全领域的 DAO
- 至少需要配置一个 Realm,也可配置多个
- 封装了数据源的连接细节,提供统一访问接口
- 支持多种数据源类型,可自定义实现
Realm 的核心功能
身份验证getAuthenticationInfo方法
- 作用:验证账户和密码,返回用户验证信息
- 处理流程:
- 接收用户提交的 Token(如用户名和密码)
- 从数据源获取用户存储的验证信息
- 比较 Token 与存储的信息,完成验证
- 返回验证通过的用户信息
- Token 示例(UsernamePasswordToken):
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
private String username; // 用户名
private char[] password; // 密码
private boolean rememberMe; // 记住我
private String host; // 主机地址
}
权限获取getAuthorizationInfo方法
- 作用:获取指定用户的角色和权限信息
- 功能:返回用户被授予的角色集合和权限集合,
令牌支持supports方法
- 作用:判断 Realm 是否支持某种类型的 Token
- 常用场景:通常支持 UsernamePasswordToken,也可扩展支持其他类型(如 HostAuthenticationToken)
Realm 的方法执行时机
身份验证方法getAuthenticationInfo
- 触发时机:当调用
subject.login(token)时执行 - 示例代码中的触发点:
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token); // 此时触发身份验证
权限获取方法getAuthorizationInfo
- 触发时机有三种:
- 调用
subject.hasRole("角色名")或subject.isPermitted("权限名")时 - 方法上使用
@RequiresRoles("角色名")等注解时 - 页面中使用 Shiro 标签(如
[@shiro.hasPermission name="权限名"][/@shiro.hasPermission])时
- 调用
shrio的认证授权
shrio认证过程
- 构建 SecurityManager 环境,并设置 Realm
- 主体(Subject)提交认证请求(调用 login 方法)
- SecurityManager 委托 Authenticator 进行身份验证
- Authenticator 可能委托 AuthenticationStrategy 处理多 Realm 验证
- Authenticator 将 token 传入 Realm 获取身份信息,无返回或抛出异常则认证失败
流程触发层
// 开发者可见的调用入口
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(new UsernamePasswordToken("admin", "password123"));
- 前置条件:必须通过
SecurityUtils.setSecurityManager()初始化安全管理器
核心控制层
SecurityManager作为中央调度器:
- 接收Subject提交的认证请求
- 委派给内置的
Authenticator组件执行具体验证 - 管理整个认证过程的生命周期
认证执行层
Authenticator(默认实现ModularRealmAuthenticator):
@startuml
Authenticator -> AuthenticationStrategy : 应用验证策略
AuthenticationStrategy -> Realm1 : 查询凭证
AuthenticationStrategy -> Realm2 : 查询凭证
@enduml
- 支持通过
setAuthenticator()注入自定义实现 - 采用策略模式处理多Realm场景
策略决策层
AuthenticationStrategy控制多Realm协作方式:
| 策略类型 | 行为特征 | 适用场景 |
|---|---|---|
| AtLeastOneSuccess | 任意Realm验证成功即通过 | 多认证源并联 |
| FirstSuccessful | 采用首个成功的验证结果 | 认证源优先级排序 |
| AllSuccessful | 要求全部Realm验证成功 | 多因素认证 |
数据验证层
Realm实际执行凭证校验:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) throw new UnknownAccountException();
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
- 验证失败时抛出具体异常:
IncorrectCredentialsException:密码错误LockedAccountException:账户锁定
- 支持配置多个Realm形成认证链
图片参考:https://blog.csdn.net/qq_45299673/article/details/122091352

校验过程

流程如下:
授权请求入口层
// 开发者调用方式示例
Subject subject = SecurityUtils.getSubject();
boolean hasAccess = subject.isPermitted("user:delete");
boolean hasRole = subject.hasRole("admin");
核心处理组件
| 组件 | 职责说明 | 默认实现类 |
|---|---|---|
| SecurityManager | 授权请求的中转调度 | DefaultSecurityManager |
| Authorizer | 授权逻辑的抽象接口 | ModularRealmAuthorizer |
| PermissionResolver | 权限字符串转换器 | WildcardPermissionResolver |
多Realm处理机制
ModularRealmAuthorizer 的工作逻辑:
-
遍历所有配置的Realm
-
检查Realm是否实现
Authorizer接口 -
对符合条件的Realm调用
springboot中shrio认证授权逻辑
导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
执行流程
自定义Realm

public class AccountRealm extends AuthorizingRealm {
/*
* doGetAuthorizationInfo:权限校验
* 获取用户权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
AccountProfile profile =(AccountProfile) principal.getPrimaryPrincipal();
return info;
}
/*
* doGetAuthenticationInfo:认证校验
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());
}
}
认证和授权的过程通常需要把这两个方法实现就可以
认证
通常有两种写法,可以对着流程图

方法一:直接用Jwt种的token进行认证,无需要密码验证
*
* doGetAuthenticationInfo:认证校验
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
JwtToken jwtToken = (JwtToken) authenticationToken;
// 校验jwt
Claims claim = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal());
if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
throw new UnauthenticatedException("请重新登录");
}
//获取到用户的信息,这里放到claim中,在设计的产生token的过程中
//以下是业务逻辑
String userId = claim.getSubject();
SysUser sysUser = userService.getById(Long.valueOf(userId));
if (sysUser == null) {
throw new UnknownAccountException("账户不存在");
}
if (sysUser.getStatus() == -1) {
throw new LockedAccountException("账户已被锁定");
}
AccountProfile profile = new AccountProfile();
BeanUtil.copyProperties(sysUser, profile);
/
return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());
}
方法一最重要的是通过return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());进行返回。
参数一为用户,参数二为token,参数三为realm
方法二:需要进行密码,以及加盐的过程
参考:https://blog.csdn.net/hubeilihao/article/details/106414363
public class ShiroRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 第一步从token中取出用户名
String userName = (String) token.getPrincipal();
// 第二步:根据用户输入的userName从数据库查询
User user = userService.findByUsername("userName");
if(user==null){
return null;//用户不存在
}
//第三步 从数据库取该用户的passw
String password = user.getPassword();
// 第四步 加盐
String salt = userCode;
.......其他判断逻辑......
// 第五步 创建SimpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,password,ByteSource.Util.bytes(salt), this.getName());
//第六步 返回
return simpleAuthenticationInfo;// return的过程完成 password的验证
}
}
授权逻辑

/*
* doGetAuthorizationInfo:权限校验
* 获取用户权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
AccountProfile profile =(AccountProfile) principal.getPrimaryPrincipal();
Long userId = profile.getId();
//获取角色
List<SysRole> roles = sysRoleService.listRolesByUserId(userId);
//获取菜单
List<SysMenu> menus = sysMenuService.listMenusByUserId(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles.stream().map(SysRole::getCode).collect(Collectors.toSet()));
info.setStringPermissions(menus.stream().map(SysMenu::getPerms).collect(Collectors.toSet()));
return info;
}
PrincipalCollection 是 Shiro 中表示用户身份信息的集合接口,它:
- 存储了一个或多个"主体"(Principal)
- 主体代表用户的身份信息(如用户对象、用户名等)
- 通常来自认证阶段设置的
AuthenticationInfo
// 获取主要主体(通常是认证时设置的第一个主体)
Object getPrimaryPrincipal()
// 获取所有主体(当使用多Realm认证时可能有多个)
Collection<?> getPrincipals()
- 从
PrincipalCollection获取主要主体 - 强制转换为自定义的
AccountProfile类型(这是在认证阶段存入的用户概要信息)
最重要的一点是这个,这里都用set集合
//创建 SimpleAuthorizationInfo 对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//设置角色集合(提取角色编码)
info.setRoles(roles.stream().map(SysRole::getCode).collect(Collectors.toSet()));
//设置权限字符串集合(提取权限标识)
info.setStringPermissions(menus.stream().map(SysMenu::getPerms).collect(Collectors.toSet()));
- 使用
@RequiresRoles或@RequiresPermissions注解时 - 调用
subject.hasRole()或subject.isPermitted()时 - 首次进行权限检查时(结果会被缓存)
Shiro配置
@Configuration
public class ShiroConfig {
//自定义Realm永远完成具体的认证和授权操作
// Realm的父类抽象类
// AuthenticatingRealm 只负责认证(登录)的Realm父类
// AuthorizingRealm 负责认证(登录)和授权 的Realm父类
@Bean
public Realm realm() {
return new AccountRealm();
}
/**
* 配置ShiroFilterChainDefinition
* @return
* 定义 URL 路径与 Shiro 过滤器的映射关系,即哪些路径需要什么样的安全控制。
* 默认情况下,Shiro 框架会为所有路径添加一个 "authc" 过滤器,即要求用户进行身份验证。
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
chain.addPathDefinition("/app/**", "anon");
chain.addPathDefinition("/sys/login", "anon");
chain.addPathDefinition("/**", "jwt");
return chain;
}
/**
* 配置ShiroFilterFactoryBean
* @param securityManager
* @param shiroFilterChainDefinition
* @return
* SecurityManager:Shiro 的核心安全管理器(由 Spring 自动注入)
* ShiroFilterChainDefinition:前面定义的 URL 过滤规则(由 Spring 自动注入)
*置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
* //例如什么样的请求可以访问,什么样的请求不可以访问等等
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition) {
//这是 Shiro 提供的工厂类,用于创建过滤器链
// 创建Shiro的拦截的拦截器 ,用于拦截我们的用户请求
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
//设置Shiro的安全管理,设置管理的同时也会指定某个Realm 用来完成我们权限分配
shiroFilter.setSecurityManager(securityManager);
/**
* shiroFilter.setFilters(MapUtil.of("jwt", new JwtFilter()));
* 使用 Hutool 的 MapUtil.of() 创建了一个单元素 Map
* Key "jwt":过滤器名称(在路径规则中引用)
* Value new JwtFilter():自定义的 JWT 过滤器实例
* 这样就将自定义的 JwtFilter 注册到了 Shiro 过滤器系统中
*/
shiroFilter.setFilters(MapUtil.of("jwt",new JwtFilter()));
//获取并设置过滤器链映射
//从 shiroFilterChainDefinition 获取之前定义的路径-过滤器映射
//(即 /app/**=anon, /sys/login=anon, /**=jwt)
//将这些映射设置到 shiroFilter 中
//定义一个Map集合,这个Map集合中存放的数据全部都是规则,用于设置通知Shiro什么样的请求可以访问什么样的请求不可以访问
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
}
() 创建了一个单元素 Map
* Key "jwt":过滤器名称(在路径规则中引用)
* Value new JwtFilter():自定义的 JWT 过滤器实例
* 这样就将自定义的 JwtFilter 注册到了 Shiro 过滤器系统中
*/
shiroFilter.setFilters(MapUtil.of("jwt",new JwtFilter()));
//获取并设置过滤器链映射
//从 shiroFilterChainDefinition 获取之前定义的路径-过滤器映射
//(即 /app/**=anon, /sys/login=anon, /**=jwt)
//将这些映射设置到 shiroFilter 中
//定义一个Map集合,这个Map集合中存放的数据全部都是规则,用于设置通知Shiro什么样的请求可以访问什么样的请求不可以访问
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
}

浙公网安备 33010602011771号