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(权限 / 菜单表)

存储系统中所有可操作的权限(目录、菜单、按钮),对应 “创建文档”“查看文档” 等具体权限。

idparent_idtitlepathpermscomponenttypecreatedsort_ordericonstatusupdated
10文档管理/documentNULLLayout02024-01-01 00:00:001folder02024-01-01 00:00:00
21文档列表/document/listdocument:listDocumentList12024-01-01 00:00:002list02024-01-01 00:00:00
32创建文档NULLdocument:createNULL22024-01-01 00:00:003plus02024-01-01 00:00:00
42查看文档NULLdocument:viewNULL22024-01-01 00:00:004eye02024-01-01 00:00:00
52编辑文档NULLdocument:editNULL22024-01-01 00:00:005edit02024-01-01 00:00:00
62删除文档NULLdocument:deleteNULL22024-01-01 00:00:006trash02024-01-01 00:00:00
70系统管理/systemNULLLayout02024-01-01 00:00:007setting02024-01-01 00:00:00
87用户管理/system/useruser:manageUserManage12024-01-01 00:00:008user02024-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”。

idnamecoderemarkcreatedupdated
1超级管理员ROLE_ADMIN拥有系统所有权限2024-01-01 00:00:002024-01-01 00:00:00
2部门经理ROLE_MANAGER拥有文档全操作权限2024-01-01 00:00:002024-01-01 00:00:00
3普通员工ROLE_EMPLOYEE拥有文档创建 / 查看 / 编辑权限2024-01-01 00:00:002024-01-01 00:00:00
4访客ROLE_GUEST仅拥有文档查看权限2024-01-01 00:00:002024-01-01 00:00:00

解释

  • code字段为角色标识,用于权限校验(如ROLE_ADMIN对应管理员)。
  • 角色权限范围与前文一致:Admin > Manager > Employee > Guest。
sys_role_menu(角色 - 权限关联表)

关联角色与权限,定义每个角色可操作的具体权限。

idrole_idmenu_id
113(Admin 拥有 “创建文档” 权限)
214(Admin 拥有 “查看文档” 权限)
315(Admin 拥有 “编辑文档” 权限)
416(Admin 拥有 “删除文档” 权限)
518(Admin 拥有 “用户管理” 权限)
623(Manager 拥有 “创建文档” 权限)
724(Manager 拥有 “查看文档” 权限)
825(Manager 拥有 “编辑文档” 权限)
926(Manager 拥有 “删除文档” 权限)
1033(Employee 拥有 “创建文档” 权限)
1134(Employee 拥有 “查看文档” 权限)
1235(Employee 拥有 “编辑文档” 权限)
1344(Guest 仅拥有 “查看文档” 权限)

解释

  • 角色权限严格遵循前文规则:Admin 拥有所有权限(P1-P5),Manager 缺少 “管理用户”(P5),Employee 缺少 “删除文档”(P4)和 “管理用户”(P5),Guest 仅保留 “查看文档”(P2)。
sys_user(用户表)

存储系统用户信息,对应 “张三、李四、王五、访客 001”。

idusernamepasswordavataremailphonecreatedupdatedlast_loginstatusis_delete
1zhangsan$2a101010xxxxxx(加密后)/avatar/zhangsan.jpgzhangsan@example.com138001380002024-01-01 00:00:00NULL2024-07-16 09:00:0000
2lisi$2a101010xxxxxx(加密后)/avatar/lisi.jpglisi@example.com139001390002024-01-02 00:00:00NULL2024-07-16 09:30:0000
3wangwu$2a101010xxxxxx(加密后)/avatar/wangwu.jpgwangwu@example.com137001370002024-01-03 00:00:00NULL2024-07-16 10:00:0000
4guest001$2a101010xxxxxx(加密后)/avatar/guest.jpgguest001@example.comNULL2024-07-16 08:00:00NULL2024-07-16 08:30:0000

解释

  • password字段存储加密后的密码(如 BCrypt 加密),避免明文泄露。
  • status=0表示用户正常,is_delete=0表示未删除(逻辑删除标记)。
  • last_login记录最近登录时间,用于追踪用户活动。
sys_user_role(用户 - 角色关联表)

关联用户与角色,定义每个用户所属的角色。

iduser_idrole_id
111(张三→超级管理员)
222(李四→部门经理)
333(王五→普通员工)
444(访客 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.youkuaiyun.com/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 的工作逻辑:

  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

在这里插入图片描述

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.youkuaiyun.com/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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值