一、RBAC的基本概念
RBAC模型通过角色与权限的关联,实现用户与权限的逻辑分离,从而简化权限管理。在RBAC中,有三个核心要素:用户、角色和权限。

用户:需要访问系统资源的个体。每个用户都有不同且唯一的ID,用来进行识别,并被授予不同的角色。
角色:一组权限的集合,代表某种职责或功能。例如,在企业管理系统中,可以有“管理员”、“普通员工”、“财务人员”等角色。
权限:对特定资源的访问能力,如读取、写入、执行等。
二、RBAC的工作原理
RBAC通过将权限分配给角色,再将角色分配给用户,从而控制用户对系统资源的访问。这个过程可以概括为:判断“Who(用户)是否可以对What(资源)进行How(操作)的访问”,并判断这个逻辑表达式的值是否为True。

角色分配:将角色分配给用户的过程。用户可以通过被分配到一个或多个角色来获得相应的权限。
权限分配:将权限分配给角色的过程。每个角色都被赋予特定的权限,这些权限决定了用户在系统中可以执行哪些操作。

三. Shiro授权的基本概念
授权:也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面、编辑数据、页面操作等)。

关键对象:

主体(Subject):访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
资源(Resource):在应用中用户可以访问的URL,比如访问JSP页面、查看或编辑某些数据、访问某个业务方法、打印文本等都是资源。用户只有授权后才能访问。
权限(Permission):安全策略中的原子授权单位,通过权限可以表示在应用中用户有没有操作某个资源的权力,如访问用户列表页面、查看或新增、修改、删除用户数据等。Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)。
角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

添加Shiro依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <classifier>jakarta</classifier>
            <version>2.0.1</version>
            <!-- 排除仍使用了javax.servlet的依赖shiro-core、shiro-web包 -->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
配置Shiro
@Configuration
public class ShiroConfig {
 
    // 注入Redis参数,从application.yml获得
    @Value("${spring.data.redis.host}")
    private String host;
    @Value("${spring.data.redis.port}")
    private int port;
    @Value("${spring.data.redis.password}")
    private String password;
    @Value("${spring.data.redis.timeout}")
    private int timeout;
 
    @Resource
    private RoleService roleService;
 
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
        // thymeleaf页面上实用shiro标签
        return new ShiroDialect();
    }
    /**
     * 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),
     * 需借助SpringAop扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置一下两个Bean(DefaultAdvisorAutoProxyCreator和        
      AuthorizationAttributeSourceAdvisor)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
 
    /*
     * 开启aop注解支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
 
 
    @Bean
    public MyShiroRealm myShiroRealm() {    // 自定义Realm
        MyShiroRealm shiroRealm = new MyShiroRealm();
        // 设置启用缓存,并设置缓存名称
        shiroRealm.setCachingEnabled(true);
        return shiroRealm;
    }
 
    @Bean
    public SecurityManager securityManager(){       // 安全管理器SecurityManager
        DefaultWebSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
        // 注入Realm
        defaultSecurityManager.setRealm(myShiroRealm());
        // 注入缓存管理器
        defaultSecurityManager.setCacheManager(cacheManager());
        // 注入会话管理器
        defaultSecurityManager.setSessionManager(sessionManager());
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        return defaultSecurityManager;
    }
 
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactory=new ShiroFilterFactoryBean();
        // 注入SecurityManager
        shiroFilterFactory.setSecurityManager(securityManager);
 
        // 权限验证:使用Filter控制资源(URL)的访问
        shiroFilterFactory.setLoginUrl("/index");
        shiroFilterFactory.setSuccessUrl("/main");
        shiroFilterFactory.setUnauthorizedUrl("/403");      // 没有权限跳转403页面
 
        Map<String,String> filterChainDefinitionMap=new LinkedHashMap<String, String>();    // 必须使用LinkedHashMap(有序集合)
        // 配置可以匿名访问的资源(URL):静态资源
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/fonts/**","anon");
        filterChainDefinitionMap.put("/images/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/localcss/**","anon");
        filterChainDefinitionMap.put("/localjs/**","anon");
 
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/logout","logout");
        //配置需要特定权限才能访问的资源(URL)
 
        //静态授权:包括全部需要特定权限才能访问的资源(URL)
        filterChainDefinitionMap.put("/user/list","perms[用户列表]");
        filterChainDefinitionMap.put("/user/add","perms[用户添加]");
        filterChainDefinitionMap.put("/user/edit","perms[用户编辑]");
        filterChainDefinitionMap.put("/user/del","perms[用户删除");
 
 
 
        //配置认证访问:其他资源(URL)必须认证通过才能访问;
        filterChainDefinitionMap.put("/**","authc"); //必须放在过滤器链的最后面
 
        shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactory;
    }
 
 
}

创建一个自定义的Realm类,用于处理用户的认证和授权逻辑。例如:

public class MyShiroRealm extends AuthorizingRealm {        // 安全数据源
    @Resource
    private UserService userService;
 
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("调用MyShiroRealm.doGetAuthenticationInfo获取身份信息!");
        // 获得身份信息
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        String usrName=token.getUsername();
        User user=userService.getUserByUsrName(usrName);
        if (user==null){
            throw new UnknownAccountException();  // 账号错误
        }
        if (user.getUsrFlag()==null||user.getUsrFlag().intValue()==0){
            throw new LockedAccountException();   // 账号锁定
        }
        // user身份信息,user.getUsrPassword()密码,getName()realm名称
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getUsrPassword(),getName());
        // 返回身份信息
        return info;
    }
 
 
 
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("调用MyShiroRealm.doGetAuthorizationInfo获取权限信息!");
        // 获取权限信息
        User user=(User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        // 静态授权:授予主题(用户)相应的角色和权限
        info.addRole(user.getRole().getRoleName());
        info.addStringPermission("用户列表");       // 所有用户拥有“用户列表”权限
        if ("管理员".equals(user.getRole().getRoleName())){
            info.addStringPermission("用户添加");
            info.addStringPermission("用户编辑");
            info.addStringPermission("用户删除");
        }
     
        return info;
    }
}

在Spring Boot的控制器中,可以通过注解来实现权限控制。例如:

@Controller
public class indexController {
    @Resource
    private UserService userService;
    @Resource
    private RoleService roleService;
 
    @RequestMapping(value = "/403")
    public String unauthorized(){
        return "403";
    }
 
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
 
    @RequestMapping("/main")
    public String toMain(){
        return "main";
    }
 
    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.removeAttribute("loginUser");
        session.invalidate();
        SecurityUtils.getSubject().logout(); // Shiro注销
        return "redirect:/login";
    }
 
    @PostMapping("/login")
    public String doLogin(String usrName, String usrPassword, HttpServletRequest request,Model model,HttpSession session){
        try {
            UsernamePasswordToken token=new UsernamePasswordToken(usrName,usrPassword);
            Subject subject= SecurityUtils.getSubject();
            subject.login(token);
            // 认证(登录)成功
            User user=(User) subject.getPrincipal();
            // 获取权限
            Role role=user.getRole(); // 如果一对多关联不是立即加载,则需要通过用户获取校色
            List<Right> rights=roleService.findRightsByRole(role);
            role.getRights().addAll(rights);
 
            session.setAttribute("loginUser",user);
            return "redirect:/main";
        }catch (UnknownAccountException | IncorrectCredentialsException e){
            model.addAttribute("message","用户名或密码失败,登录失败!");
            return "login";
        }catch (LockedAccountException e){
            model.addAttribute("message","用户被禁用,登录失败!");
            return "login";
        }catch (AuthenticationException e){
            model.addAttribute("message","认证异常,登录失败!");
            return "login";
        }
    }
}
10-24 17:34