Springboot中的Shiro框架

Springboot中的Shiro框架

首先了解下原理,了解下shrio的认证的逻辑,再讲解下springboot中,如何通过代码进行认证,授权操作。

Shiro框架的逻辑

RBAC模型

在讲解认证授权之前,先介绍下RBAC模型,Shiro框架后续用上的最后本质上,还是通过查询这个库。

  • 定义:RBAC(Role-Based Access Control)即基于角色的访问控制模型,核心是通过 “用户 - 角色 - 权限” 的层级关系实现访问控制,简化权限管理流程。

  • 核心要素

    • 用户(User):系统的实际使用者,可被分配多个角色。
    • 角色(Role):一组相关权限的集合,代表用户的职能或职位(如管理员、普通用户)。
    • 权限(Permission):对系统资源的操作许可(如查看、编辑、删除等),可关联到角色。
    • 关系:用户与角色、角色与权限均为多对多关系。
用户层
角色层
权限层
拥有
拥有
拥有
拥有
拥有
拥有
拥有
拥有
拥有
拥有
拥有
拥有
拥有
分配
分配
分配
分配
张三
李四
王五
访客001
Admin
Manager
Employee
Guest
P1: 创建文档
P2: 查看文档
P3: 编辑文档
P4: 删除文档
P5: 管理用户

如何做到不同的角色有不同的权限。

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
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 email phone created updated last_login status is_delete
1 zhangsan $2a101010xxxxxx(加密后) /avatar/zhangsan.jpg [email protected] 13800138000 2024-01-01 00:00:00 NULL 2024-07-16 09:00:00 0 0
2 lisi $2a101010xxxxxx(加密后) /avatar/lisi.jpg [email protected] 13900139000 2024-01-02 00:00:00 NULL 2024-07-16 09:30:00 0 0
3 wangwu $2a101010xxxxxx(加密后) /avatar/wangwu.jpg [email protected] 13700137000 2024-01-03 00:00:00 NULL 2024-07-16 10:00:00 0 0
4 guest001 $2a101010xxxxxx(加密后) /avatar/guest.jpg [email protected] 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_idrole_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),它:

    1. 封装了与安全数据源(数据库、LDAP等)的连接细节
    2. 提供统一的API供 SecurityManager 调用
    3. 负责将数据源的原始数据转换为 Shiro 可识别的安全信息

    类比理解:就像JDBC连接数据库,Realm是Shiro连接各种安全数据源的标准接口。这里不好解释,就是类似存放了相关用户数据。通过一些列操作之后得到相关的用户的数据。

    下面有关realm不了解可以先跳过,博主也暂时能力有限,没有找到更好的表述方式,可以暂时先跳过。跟着流程表述就行,这里还是比较晦涩难懂。写到这里博主觉得,可以先跟着后面的springboot是如何是如何使用shrio框架的进行过一遍流程。有不懂得地方再进行查阅,先弄懂相关的认证、授权的逻辑。先过一遍,后面再慢慢的一点的弄懂。先用着,再慢慢的了解其特性。

Realm 的基本概念

Realm(域)是 Shiro 框架中连接应用与安全数据源的 “桥梁” 或 “连接器”。当进行认证(登录)和授权(访问控制)验证时,Shiro 会通过 Realm 获取用户及其权限信息。

其核心特点包括:

  • 是 Shiro 的安全数据访问层,相当于安全领域的 DAO
  • 至少需要配置一个 Realm,也可配置多个
  • 封装了数据源的连接细节,提供统一访问接口
  • 支持多种数据源类型,可自定义实现

Realm 的核心功能

身份验证getAuthenticationInfo方法
  • 作用:验证账户和密码,返回用户验证信息
  • 处理流程:
    1. 接收用户提交的 Token(如用户名和密码)
    2. 从数据源获取用户存储的验证信息
    3. 比较 Token 与存储的信息,完成验证
    4. 返回验证通过的用户信息
  • 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
  • 触发时机有三种:
    1. 调用subject.hasRole("角色名")subject.isPermitted("权限名")
    2. 方法上使用@RequiresRoles("角色名")等注解时
    3. 页面中使用 Shiro 标签(如[@shiro.hasPermission name="权限名"][/@shiro.hasPermission])时

shrio的认证授权

shrio认证过程
  1. 构建 SecurityManager 环境,并设置 Realm
  2. 主体(Subject)提交认证请求(调用 login 方法)
  3. SecurityManager 委托 Authenticator 进行身份验证
  4. Authenticator 可能委托 AuthenticationStrategy 处理多 Realm 验证
  5. Authenticator 将 token 传入 Realm 获取身份信息,无返回或抛出异常则认证失败
成功
失败
Subject.login
SecurityManager
Authenticator
AuthenticationStrategy
Realm.getAuthenticationInfo
创建Subject
抛出异常
Subject.checkPermission
SecurityManager
Authorizer
Realm.doGetAuthorizationInfo
数据库/缓存
PermissionResolver
权限比对
返回结果
流程触发层
// 开发者可见的调用入口
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

Springboot中的Shiro框架_第1张图片

校验过程

Springboot中的Shiro框架_第2张图片

流程如下:

授权请求入口层
// 开发者调用方式示例
Subject subject = SecurityUtils.getSubject();
boolean hasAccess = subject.isPermitted("user:delete"); 
boolean hasRole = subject.hasRole("admin");
核心处理组件
组件 职责说明 默认实现类
SecurityManager 授权请求的中转调度 DefaultSecurityManager
Authorizer 授权逻辑的抽象接口 ModularRealmAuthorizer
PermissionResolver 权限字符串转换器 WildcardPermissionResolver
多Realm处理机制

ModularRealmAuthorizer 的工作逻辑:

  1. 遍历所有配置的Realm

  2. 检查Realm是否实现Authorizer接口

  3. 对符合条件的Realm调用

springboot中shrio认证授权逻辑

导入依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.10.0</version>
</dependency>

执行流程

自定义Realm

Springboot中的Shiro框架_第3张图片

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());
    }
}


认证和授权的过程通常需要把这两个方法实现就可以

认证

通常有两种写法,可以对着流程图

Springboot中的Shiro框架_第4张图片

方法一:直接用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的验证
}

}
授权逻辑

Springboot中的Shiro框架_第5张图片

 /*
    * 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;
    }
}

你可能感兴趣的:(java,开发语言)