当前位置: 首页 > news >正文

Shiro学习(三):shiro整合springboot

一、Shiro整合到Springboot步骤

1、准备SpringBoot 环境,这一步省略

2、引入Shiro 依赖

     因为是Web 项目,所以需要引入web 相关依赖 shiro-spring-boot-web-starter,如下所示:

     

3、准备Realm

     因为实例化 ShiroFilterFactoryBean 时需要注入  SecurityManager 的bean,而 

     SecurityManager 实例化时需要绑定Realm。

      在真正工作中,我们一般需要从数据库中查询用户信息、角色信息和用户权限信息,

      即一般自定义Relam,自定义Realm如下:

@Component
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    {
        //用于密码加密和比对
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(1024);
        this.setCredentialsMatcher(matcher);
    }

    /**
     * 授权
     * todo 注意:
     *    1、授权是在认证之后的操作,授权方法需要用到认证方法返回的 AuthenticationInfo  中的用户信息
     *    2、该方法是在父类 AuthorizingRealm.getAuthorizationInfo() 方法中调用的,在 getAuthorizationInfo()
     *          方法中,回先从缓存中查询权限信息,若缓存中数据不存在,再执行改当前方法从数据库中查询权限数据
     *          针对这个逻辑,我们可以扩展Shiro 把数据放到缓存中(一般放到redis 中)
     *
     *
     * @param principals  即 doGetAuthenticationInfo 方法返回的 AuthenticationInfo 中的用户信息(这里是User )
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("查询数据库,根据用户信息查询角色和权限信息~~~~~~~~~~~~~~~~~~~~~~~~");

        //0、判断是否完成认证
        Subject subject = SecurityUtils.getSubject();
        if(subject == null || subject.isAuthenticated())
            return null;

        //1. 获取认证用户的信息
        User user = (User) principals.getPrimaryPrincipal();

        //2. 基于用户信息获取当前用户拥有的角色。
        Set<Role> roleSet = roleService.findRolesByUid(user.getId());
        Set<Integer> roleIdSet = new HashSet<>();
        Set<String> roleNameSet = new HashSet<>();
        for (Role role : roleSet) {
            roleIdSet.add(role.getId());
            roleNameSet.add(role.getRoleName());
        }

        //3. 基于用户拥有的角色查询权限信息
        Set<Permission> permSet = permissionService.findPermsByRoleSet(roleIdSet);
        Set<String> permNameSet = new HashSet<>();
        for (Permission permission : permSet) {
            permNameSet.add(permission.getPermName());
        }

        //4. 声明AuthorizationInfo对象作为返回值,传入角色信息和权限信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roleNameSet);
        info.setStringPermissions(permNameSet);

        //5. 返回
        return info;
    }

    /**
     * 认证 用户执行认证操作传入的用户名和密码
     * 只需要完成用户名校验即可,密码校验由Shiro内部完成
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //1、获取用户名称
        String userName = (String) token.getPrincipal();

        //2、判断用户名称是否为空
        if(StringUtils.isEmpty(userName)){
            // 返回null,会默认抛出一个异常,org.apache.shiro.authc.UnknownAccountException
            return null;
        }
        //4、如果用户名称不为空,则基于用户名称去查询用户信息
        //这一步一般是自己的UserService 服务
        //模拟查询用户信息
        User user = userService.findByUsername(userName);
        if(user == null){
            return null;
        }

        //5、构建 AuthenticationInfo 对象,并填充用户信息
        /**
         * todo 注意:
         *    SimpleAuthenticationInfo 第一个参数是用户信息,第二个参数是用户密码,第三个参数是Realm名称(这个参数没有意义)
         */
        SimpleAuthenticationInfo info =  new SimpleAuthenticationInfo(user,user.getPassword(),"CustomRealm!!!");

        //设置盐
        info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
        //返回 AuthenticationInfo 对象
        return info;
    }

    
}

4、准备Shiro相关的配置文件

      定义Shiro 配置文件,用于实例化 SecurityManager 对象 与 配置拦截器链。

      虽然SpringBoot 自动装配机制会自动装配 SecurityManager,但自动装载 SecurityManager 时

      只会注入 Shiro 自身默认提供的Relam,这里需要把自定义的Realm注入到 SecurityManager

     ,所以需要我们手动装载 SecurityManager 去覆盖Springboot 自动装载的 SecurityManager。

     另外在 shiro-spring-boot-web-starter 包下的文件 spring.factores 中的配置类

     ShiroWebAutoConfiguration中 springboot自动装配的拦截器链中只配置了一种请求,

     如图所示:

           

     所以我们也需要在自定义的shiro配置文件中,手动配置我们需要的拦截器链。

     shiro配置文件如下:

     

/****************************************************
 * Shiro 配置类
 * 由前边 Shiro 与Spring Web 整合可以发现,Shiro与Spring 整合的核心是向spring中注入
 * ShiroFilterFactoryBean;但在spring boot 中,spring boot 会自动将 ShiroFilterFactoryBean
 * 注入到spring 中(在 AbstractShiroWebFilterConfiguration 中完成的)
 * 在 AbstractShiroWebFilterConfiguration 中发现,实例化 ShiroFilterFactoryBean 时需要
 * 提供 SecurityManager(使用的是  DefaultWebSecurityManager) 和 ShiroFilterChainDefinition(拦截器链)
 * SecurityManager 实例化需要提供 Realm ,
 *
 * 定义 Shiro 配置类,用于实例化  DefaultWebSecurityManager 和 ShiroFilterChainDefinition
 *
 * @author lbf
 * @date 
 ****************************************************/
@Configuration
public class ShiroConfig {

    /**
     * 注入
     * 实例化 WebSecurityManager 时需要用到Releam bean,所以在这之前 Releam 一定要存在
     * @param realm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(CustomRealm realm, SessionManager sessionManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);


        return securityManager;
    }

    /**
     * 添加拦截器链
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition filterChainDefinition(){
        DefaultShiroFilterChainDefinition filterChainDefinition = new DefaultShiroFilterChainDefinition();

        //添加拦截器信息
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap();
        /**
         * anon 和 authc 都是Shiro 自带的过滤器
         * Shiro 自带的过滤器可以在枚举 DefaultFilter 中查看
         */
        //anon: 放行
        filterChainDefinitionMap.put("/login.html","anon");
        filterChainDefinitionMap.put("/user/**","anon");
       
        //authc:认证之后放行
        filterChainDefinitionMap.put("/**","authc");

        filterChainDefinition.addPathDefinitions(filterChainDefinitionMap);

        return filterChainDefinition;

    }

    
}

    

5、测试

      

二、Shiro 授权方式

       Shiro 常用的授权方式有2种,即

           1)基于连接器链的权限角色校验,就是上边配置拦截器链的方式;将需要校验的请求

                 配置到拦截器链 ShiroFilterChainDefinition 种,如下图所示:

                     

           2)基于注解的权限角色校验

   

    2、基于注解的权限角色校验

          Shiro 提供了基于注解的方式来简化权限和角色的校验,可以在类或方法上直接声明所需要

          的角色或权限;

          注解进行权限或角色校验时,是基于对Controller类进行代理,在前置增强中对请求进行权限

          校验,是在拦截器链方式的后边执行。

          Shiro 提供了如下几个注解用于权限和角色校验

                 1)@RequiresAuthentication

                       要求当前 Subject 已经通过认证(即用户已登录)

                 2)@RequiresUser

                       要求当前 Subject 是一个应用程序用户(已认证或通过记住我功能登录)

                 3)@RequiresGuest

                       要求当前 Subject 是一个"访客",即未认证或未通过记住我登录

                 4)@RequiresRoles

                      要求当前 Subject 拥有指定的角色,即角色校验,常用

                 5)@RequiresPermissions

                      要求当前 Subject 拥有指定的权限,即权限校验,常用

          示例代码如下:

@RestController
@RequestMapping("/item")
public class ItemController {

    /**
     * 基于过滤器链的角色、权限校验
     * @return
     */
    @GetMapping("/select")
    public String select(){
        return "item Select!!!";
    }

    @GetMapping("/delete")
    public String delete(){
        return "item Delete!!!";
    }

    /**
     * 基于注解的角色校验
     * @return
     */
    @GetMapping("/update")
    //默认多个角色是and 的关系
    @RequiresRoles(value = {"超级管理员","运营"})
    public String update(){
        return "item Update!!!";
    }

    @GetMapping("/insert")
    @RequiresRoles(value = {"超级管理员","运营"},logical = Logical.OR)
    public String insert(){
        return "item Update!!!";
    }

    /**
     * 基于注解的权限校验
     * logical=用于指定多个权限是同时满足,还是满足其中一个,默认是and
     * Logical.OR含义是:只有用于 admin 权限或del权限的用户才能执行删除操作
     * @return
     */
    @GetMapping("/update")
    @RequiresPermissions(value =  {"item:admin","item:del"},logical = Logical.OR)
    public String del(){
        return "item del !!!";
    }
}

         

3、基于注解方式的权限角色校验要注意的点

     1)基于注解的方式与基于配置链的方式是可以配合使用的,并不冲突,基于配置的方式在

           拦截器链之后执行。

     2)注解只能用在 Spring 管理的 Bean 上(如 Controller、Service 等),对于静态方法

           或非 Spring 管理的类,注解不会生效

     3)当权限校验失败时,Shiro 会抛出相应的异常,我们需要自己配置异常处理器处理这些异常

          ,如:通过 @RestControllerAdvice,@ControllerAdvice

             示例代码如下:

@RestControllerAdvice(basePackages = "com.msb.controller") //指定要处理异常的包路径
public class AuthExceptionHandler {

    //处理授权异常
    @ExceptionHandler(AuthorizationException.class)
    public ResponseEntity<String> handleAuthorizationException(AuthorizationException e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body("无权访问: " + e.getMessage());
    }

    //处理认证异常
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body("认证失败: " + e.getMessage());
    }
}

    

三、RolesAuthorizationFilter 分析

       以默认的角色拦截器 RolesAuthorizationFilter 分析下 Shiro 种是如何进行角色认证的

       1)角色校验方法 RolesAuthorizationFilter.isAccessAllowed

//mappedValue: 需要校验的角色列表,即在连接器链种指定的角色列表
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        //获取请求主体 Subject,即用户
        Subject subject = this.getSubject(request, response);
        //需要校验的角色列表
        String[] rolesArray = (String[])((String[])mappedValue);
        if (rolesArray != null && rolesArray.length != 0) {
            Set<String> roles = CollectionUtils.asSet(rolesArray);
            //调用 Subject.hasAllRoles 校验用户是否具有所有的 mappedValue 角色
            return subject.hasAllRoles(roles);
        } else {
            return true;
        }
    }

        2)AuthorizingRealm.hasAllRoles

              从Subject.hasAllRoles 方法一直点进去 ,如下所示:

                    Subject—>DelegatingSubject.hasAllRoles —> Authorizer.hasAllRoles

                     —> AuthorizingRealm.hasAllRoles

              一直到 AuthorizingRealm.hasAllRoles 方法,在该方法种调用了getAuthorizationInfo 

             方法来获取 AuthorizationInfo,如下图所示:

                  

        3)AuthorizingRealm.getAuthorizationInfo 方法

             在 getAuthorizationInfo 方法种,我们重点看下 doGetAuthorizationInfo 方法;

             到这里是不是有点眼熟,doGetAuthorizationInfo是一个抽象方法,它有多个实现,

            其中有一个实现就是上边我们自定义的CustomRealm中的方法

             如下图所示:

                

四、自定义拦截器

       在工作中Shiro 默认提供的校验拦截器往往不能满足我们实际的需要,这就需要我们自定义

       Shiro拦截器,如:上边RolesAuthorizationFilter 是校验用户具有角色列表中的所有角色才

       校验通过,而在工作中常常有 “存在一个角色在角色列表中就行” 的场景;

1、自定义Shiro拦截器解决 “存在一个角色在角色列表中就行” 的场景

      自定义过滤器需要继承类 AuthorizationFilter,并重写 isAccessAllowed 方法

      示例代码如下:

      

public class RolesOrAuthorizationFilter extends AuthorizationFilter {

    /**
     * 用户角色校验
     * @param request
     * @param response
     * @param mappedValue   指定的角色列表,
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //获取校验主题,可以认为就是用户信息
        Subject subject = this.getSubject(request, response);
        /**
         * 获取用户指定的角色列表,就是在 初始化 ShiroFilterChainDefinition过程中指定的角色列表
         * (如:roles[超级管理员,运营],[]中的内容)
         */
        String[] rolesArray = (String[])((String[])mappedValue);
        if(rolesArray != null && rolesArray.length > 0){
            for (String role:rolesArray){
                //有一个角色校验通过,则表示校验通过,返回true
                if(subject.hasRole(role)){
                    return true;
                }
            }
        }else {
            return true;
        }

        return false;
    }
}

2、将自定义Filter 交给Shiro 管理

      自定义的角色校验器(过滤器)如何交给Shiro管理?

      由 配置类 ShiroWebFilterConfiguration 初始化 ShiroFilterFactoryBean 时可以发现,过滤器

      是在 ShiroFilterFactoryBean 实例化时交给 ShiroFilterFactoryBean 管理的;到这里就明白

      了,我们可以手动初始化 ShiroFilterFactoryBean 来覆盖springboot 的自动初始化

      ShiroFilterFactoryBean,并把自定义的 RolesOrAuthorizationFilter 过滤器交给

      ShiroFilterFactoryBean 管理。

      在配置类ShiroConfig 中手动注入ShiroFilterFactoryBean 代码如下:

//手动注入  ShiroFilterFactoryBean,覆盖 Springboot 自动装载ShiroFilterFactoryBean;
    /**
     * 初始化一些url
     * ShiroFilterFactoryBean 实例化需要的url,这些url可以在配置文件中配置
     */
    //登录url
    @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }")
    protected String loginUrl;
    //登录成功后跳转url
    @Value("#{ @environment['shiro.successUrl'] ?: '/' }")
    protected String successUrl;
    //校验失败的url
    @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }")
    protected String unauthorizedUrl;

    /**
     * 手动注入 ShiroFilterFactoryBean,覆盖 Springboot 自动装载ShiroFilterFactoryBean;
     * 用于把自定义的过滤器交给 Shiro 管理
     * @param securityManager
     * @param filterChainDefinition  过滤器链
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition filterChainDefinition){
        //创建 ShiroFilterFactoryBean
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        //设置大量的url
        filterFactoryBean.setLoginUrl(this.loginUrl);
        filterFactoryBean.setSuccessUrl(this.successUrl);
        filterFactoryBean.setUnauthorizedUrl(this.unauthorizedUrl);

        //设置安全管理器
        filterFactoryBean.setSecurityManager(securityManager);

        //设置过滤器链
        filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinition.getFilterChainMap());

        //设置自定义过滤器 ,
        // todo 注意:这里一定要手动的new出来这个自定义过滤器,如果使用Spring自动注入自定义过滤器,
        //                    会造成无法获取到Subject
        //         因为spring 自动注入 自定义过滤器 RolesOrAuthorizationFilter 初始化太早了,而RolesOrAuthorizationFilter
        //        初始化时需要做一些Shiro处理后,RolesOrAuthorizationFilter 实例才能拿到Subject
        filterFactoryBean.getFilters().put("roleOr",new RolesOrAuthorizationFilter());

        return filterFactoryBean;
    }

3、在拦截器链 ShiroFilterChainDefinition 配置使用自定义过滤器

     由上边可以知道 自定义Shiro 过滤器 RolesOrAuthorizationFilter 的名称是 “roleOr”;

     在拦截器链 ShiroFilterChainDefinition 中的配置如下:

@Bean
    public ShiroFilterChainDefinition filterChainDefinition(){
        DefaultShiroFilterChainDefinition filterChainDefinition = new DefaultShiroFilterChainDefinition();

        //添加拦截器信息,LinkedHashMap有序
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap();
        /**
         * anon 和 authc 都是Shiro 自带的过滤器
         * Shiro 自带的过滤器可以在枚举 DefaultFilter 中查看
         */
        //anon: 放行
        filterChainDefinitionMap.put("/login.html","anon");
        filterChainDefinitionMap.put("/user/**","anon");
        
        //使用自定义的过滤器,有一个角色在[超级管理员,运营] 中就校验通过
        filterChainDefinitionMap.put("/item/select","rolesOr[超级管理员,运营]");
        //filterChainDefinitionMap.put("/item/select","roles[超级管理员,运营]");
       
        filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]");
        //authc:认证之后放行
        filterChainDefinitionMap.put("/**","authc");

        filterChainDefinition.addPathDefinitions(filterChainDefinitionMap);

        return filterChainDefinition;

    }

http://www.dtcms.com/a/108393.html

相关文章:

  • 城电科技 | 探秘零碳校园:创新应用,引领绿色未来
  • PHP在Debian环境上的并发处理能力如何
  • 深度学习处理文本(6)
  • STM32实现一个简单电灯
  • 2023年12月电子学会青少年软件编程四级考级真题—新“跳7”游戏
  • OpenCV
  • 系统与网络安全------Windows系统安全(5)
  • Maya软件中的约束基础:提高角色动画制作效率的关键技术
  • 【解决】Edge浏览器硬件加速问题:无法滚动与卡顿的应对方法
  • Lumerical ------ Edge coupler design
  • Test——BUG篇
  • 掌握 Git 的艺术:Rebase 和 Merge 的使用技巧
  • MySQL 中 LOCK TABLES(手动锁表) 语句的详细说明,包括语法、使用场景、示例代码及注意事项
  • c加加学习之day02
  • ubuntu制做vsftpd的docker镜像
  • git总是链接不成功
  • Excel处理控件Spire.XLS系列教程:C# 打印 Excel 文档
  • 【算法】双指针
  • GIT ---- 解决【fatal: Authentication failed for】
  • 【案例89】达梦数据库优化器参数导致的SQL执行错误
  • 在Ubuntu20.04开发Dify插件教程,部署Dify插件脚手架
  • 深度学习 Deep Learning 第15章 表示学习
  • 针对 MySQL 数据库的详细说明,分类列出临时资源(临时表、游标、未提交事务、会话变量、预编译语句)的创建、清理方式及未清理后果,并以表格总结
  • [CH32] RISC-V汇编指令解释
  • linux下springboot项目守护进程编写
  • arm64平台下linux访问寄存器
  • Python----机器学习(线性回归:前向传播和损失函数)
  • 【C++基础知识】 C 预处理器中的 #line 指令详解
  • RabbitMQ应用2
  • Linux系统之SFTP-搭建SFTP服务器