Shiro学习(四)-- RBAC权限模型讲解

一、RBAC介绍

RBAC:基于角色的权限管理

简单的理解为:谁,扮演什么角色,被允许做什么操作

用户对象: user : 当前操作用户

角色对象:role: 表示权限操作许可权的集合

权限对象: permission:资源操作许可权

例子: 张三(user) 下载(permission) 一个高清无码的种子(资源) , 需要VIP权限(role)

简单的流程就是

张三现在是普通用户 ,需要授权 成为VIP才可以下载指定的资源。

二、权限校验的几种方式
1、 编程方式

通过写if/else授权代码块完成

	if(subject.hasRole("admin")){
		//有权限
	}
	else{
		//无权限
	}
2、注解方式:

通过再执行的java方法上放置相应的注解完成

@RequiresRoles("admin")
@RequiresPermission("employee:saves")
@RequestMapping("/save")
public void hello(){
    //有权限
}

上面中必须要有admin角色,以及对employee这个资源的save操作才可以访问hello方法

三、实例演示
1、ini方式检查用户拥有的角色

建立一个ini文件,命名shiro-permission(随意)

[users]
#用户张三的密码是666 ,此时用户具有的角色是role1,role2
zhangsan=666,role1,role2
lisi=888,role2

[roles]
#role定义的格式: 资源名:操作:实例
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create,delete权限
role2=user:create,user:delete
#角色role3对资源user拥有create权限
role3=user:create

测试

  //验证用户角色
    @Test
    public void testForShiroRoles(){
        Subject subject = ShiroUtils.getSubject("classpath:shiro-permission");
        //假设这个是用户输入的用户名
        String username = "haizhang";
        //假设这个是用户输入的密码
        String password = "111";
        //设置迭代的次数,迭代次数越大,加密生成的密文就越难以被破解
        try {
            //模拟用户名和密码
            AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);
            //模拟用户登录,会去shiro.ini中对比
            subject.login(authenticationToken);
        }
        catch (UnknownAccountException e){
            System.out.println(e.getMessage());
            System.out.println("找不到账号");
        }
        catch (IncorrectCredentialsException e){
            System.out.println(e.getMessage());
            System.out.println("密码错误");
        }

        //只有通过登录认证之后,采用去检查是否对资源拥有操作的角色
       if(subject.isAuthenticated()){
           //判断登录的用户haizhang,是否拥有role3和role2的角色,返回的是角色对应的boolean数组,true表示拥有这个角色
           boolean[] booleans = subject.hasRoles(Arrays.asList("role3", "role2"));
           System.out.println(Arrays.toString(booleans));

           //判断登录的用户haizhang是否拥有单个角色。
           boolean role1 = subject.hasRole("role1");
           System.out.println(role1);

           //进行checkRoles判断,它没有返回值,但是如果用户不存在指定的某个角色,会抛出异常
           try{
//               subject.checkRole("role5");
               subject.checkRoles("role1","role2","role3");
           }catch (Exception e){
               System.out.println(e.getMessage());
           }
       }
    }

其中上面代码的shiroUtils只是我封装的一个工具类,给出代码使用到的这部分代码:

public class ShiroUtils{

	public static Subject getSubject(String iniPath){
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(iniPath);
		SecurityManager sec = factory.getInstance();
		SecurityUtils.setSecurityManager(sec);
		return SecurityUtils.getSubject();
	}
}

代码运行的结果:
在这里插入图片描述

2、 ini方式检查用户拥有的权限

同样引用上面的ini文件,我们这里讲解如何使用subject函数验证用户角色对资源拥有的权限

 //验证用户角色
    @Test
    public void testForShiroRoles2(){
        Subject subject = ShiroUtils.getSubject("classpath:shiro-permission");
        //假设这个是用户输入的用户名
        String username = "haizhang";
        //假设这个是用户输入的密码
        String password = "111";
        //设置迭代的次数,迭代次数越大,加密生成的密文就越难以被破解
        try {
            //模拟用户名和密码
            AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);
            //模拟用户登录,会去shiro.ini中对比
            subject.login(authenticationToken);
        }
        catch (UnknownAccountException e){
            System.out.println(e.getMessage());
            System.out.println("找不到账号");
        }
        catch (IncorrectCredentialsException e){
            System.out.println(e.getMessage());
            System.out.println("密码错误");
        }

        //只有通过登录认证之后,采用去检查是否对资源拥有操作的角色
        if(subject.isAuthenticated()){
            boolean[] permitted = subject.isPermitted("user:delete", "user:create", "user:search");
            System.out.println(Arrays.toString(permitted));
            try {
                subject.checkPermission("user:delete");
                subject.checkPermission("user:update");
                subject.checkPermission("user:search");
            } catch (AuthorizationException e) {
                e.printStackTrace();
            }
        }
    }

结果:
在这里插入图片描述
说明:
上面还是使用Subject类的checkxxxisPermitted方法来验证该用户拥有的所有的角色中,是否存在一个角色拥有对资源的某个具体的操作权限,其中checkxxx如果是成功的检测存在某个权限/角色。就不会抛异常。而如果没有到某个指定的权限存在,就会抛异常。

四、采用自定义realm重写权限认证方法

上面的方式还是太过于死板,将用户拥有的所有权限和密码冰冷的写在ini文件中,这样不用于进行扩展和修改。我们使用自定义realm的方式,很方便的结合数据库进行查询。

  • 定义自己的reaml类

public class MyRoleRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "MyRoleRealm";
    }


    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String)token.getPrincipal();
        String username_db = "haizhang";
        String password_db ="111";

        if(!username_db.equals(username)){
            return null;
        }
        return new SimpleAuthenticationInfo(username_db,password_db,getName());
    }

    /**
     * @param principals 用户认证凭证信息
     * @return
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //获取用户通过认证的用户名
        String username = (String)principals.getPrimaryPrincipal();
        //模拟从数据库中查出的角色以及角色拥有的权限
        String roles[] = {"role1","role2"};
        //* 代表拥有user资源的任何操作权限
        String permission[] = {"user:*"};
        //将查出的权限角色信息,放入SimpelAuthorization中包装
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(Arrays.asList(roles));
        simpleAuthorizationInfo.addStringPermissions(Arrays.asList(permission));
        return simpleAuthorizationInfo;
    }
}
  • 定义ini文件
myRealm= com.haizhang.shirodemo.reaml.MyRoleRealm
securityManager.realms=$myRealm
  • 测试类
//验证用户角色
    @Test
    public void testForShiroRoles2(){
        Subject subject = ShiroUtils.getSubject("classpath:shiro-custom-permission");
        //假设这个是用户输入的用户名
        String username = "haizhang";
        //假设这个是用户输入的密码
        String password = "111";
        try {
            //模拟用户名和密码
            AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);
            subject.login(authenticationToken);
        }
        catch (UnknownAccountException e){
            System.out.println(e.getMessage());
            System.out.println("找不到账号");
        }
        catch (IncorrectCredentialsException e){
            System.out.println(e.getMessage());
            System.out.println("密码错误");
        }

        //只有通过登录认证之后,采用去检查是否对资源拥有操作的角色
        if(subject.isAuthenticated()){

            boolean b = subject.hasRole("role1");
            System.out.println("拥有角色role1:"+b);

            boolean[] permitted = subject.isPermitted("user:delete", "user:create", "user:search");
            System.out.println(Arrays.toString(permitted));
            try {
                subject.checkPermission("user:delete");
                subject.checkPermission("user:update");
                subject.checkPermission("user:search");
            } catch (AuthorizationException e) {
                e.printStackTrace();
            }
        }
    }

结果:
在这里插入图片描述

五、小结:整个授权流程

在这里插入图片描述

  1. 首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer
  2. Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”), 其首先会通过PermissionResolver把字符串转换成相应的Permission实例
  3. 在进行授权之前,它会调用相应的Realm进行获取Subject相应的角色/权限用于匹配传入的角色/权限;
  4. Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModeularReamlAuthorizer进行循环判断,如果匹配如isPermitted/hasRole。会返回true,否则返回false表示授权失败。
六、Shiro的三种授权配置方式
1.applicationContext-shiro.xml配置

在spring中使用xml方式进行配置某个uri需要用户拥有的的角色权限:

<property name=”filterChainDefinitions“>
   <value>
   /docs/** = authc, perms[document:read]
    /admin/** = authc, roles[admin]
   </value>
</property>

解释:

  • 访问上面/docs/开头的uri接口,需要进行授权校验(authc),必须有document:read权限才可以访问。
  • 访问上面/admin/开头的uri接口,需要有admin角色才允许访问。
2.注解方法:

2.1 开启controller类aop支持

  • 如果你是spring项目
    在springmvc.xml中配置:
<!-- 开启aop,对类代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    <!-- 开启shiro注解支持 -->
    <bean
        class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
  • 如果你是springboot项目,可以如下配置:
    定义一个ShiroConfiguration的配置类,配置如下,开启支持shiro注解
@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor
            = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

在controller方法中添加注解

@RequestMapping("/user")
@RequiresPermissions("userInfo:test")
public Result<T> getUser(){}

若还没生效,应该是aop没起作用

解决方法一
ShiroConfiguration中增加

@Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

解决方法二
在pom中引入:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

同时application.properties中补充

spring.aop.proxy-target-class=true
3.JSP授权(页面上根据权限设置菜单显示与否)

Jsp页面添加:

<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>

在这里插入图片描述
其中我们还可以通过property获取对应的用户身份属性:
在这里插入图片描述
样例:
在这里插入图片描述

七、总结:

当调用controller的一个方法,由于该 方法加了@RequiresPermissions("item:query"),shiro调用realm获取数据库中的权限信息,看"item:query"是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。

当展示一个jsp页面时,页面中如果遇到<shiro:hasPermission name="item:update">,shiro调用realm获取数据库中的权限信息,看item:update是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。

还有一种情况是有时候连接是在Ajax请求之后拼接到页面的,有时候也需要根据权限进行判断,项目中也遇到这种情况:

思路:在页面中定义一个JS全局变量,在shiro权限标签里面,如果有权限修改全局变量的值,在JS中根据全局变量的值判断是否有权限

(1)页面定义全局变量

<script>
var hasOperatingDepart=false;
</script>

(2)页面用shiro标签判断是否有权限:(有权限会执行JS脚本改变全局变量的值)
(3)JS拼接的时候根据全局变量判断是否有权限:

有时候我们需要在代码中判断用户是否有某些权限;

        // 获取用户信息
        Subject currentUser = SecurityUtils.getSubject();
        //是否有权限
        boolean permitted = currentUser.isPermitted("exammanager:factory");
        // 判断是否有全厂管理的权限,有就不添加部门ID,没有就设为当前Session中的部门ID
        String departmentId = permitted ? null : departmentIdSession;

有时候我们需要在代码中判断用户是否有某些角色:

  // 获取用户信息
       Subject currentUser = SecurityUtils.getSubject();
       boolean hasRole = currentUser.hasRole("教研室");
       boolean hasRole2 = currentUser.hasRole("院长")

上面获取的主体的权限码是我们在授权的时候塞进去的,当然我们也可以将角色码也塞进去:

package cn.xm.jwxt.shiro;


import cn.xm.jwxt.bean.system.Permission;
import cn.xm.jwxt.bean.system.User;
import cn.xm.jwxt.service.system.UserService;
import cn.xm.jwxt.utils.ValidateCheck;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* @Description 自定义realm。根据上面传下来的token去数据库查信息,查到返回一个SimpleAuthenticationInfo,查不到返回null(用于shiro认证)
*/
public class CustomRealm extends AuthorizingRealm {

   @Autowired
   private UserService userService;

   // 设置realm的名称
   @Override
   public void setName(String name) {
       super.setName("customRealm");
   }

   // realm的认证方法,从数据库查询用户信息
   @Override
   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       String userCode=(String)token.getPrincipal();//获取token的主身份(登录的username
       //自定义User bean
       User user = null;
       try {
           user =  userService.getUserByUserCode(userCode);
       } catch (Exception e) {
           e.printStackTrace();
       }
       AuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
       return authenticationInfo;
   }
   // 用于授权
   @Override
   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       //0.下面方法principals.getPrimaryPrincipal()获取的是在上面认证的时候装进AuthenticationInfo的对象
       String userId=((User)(principals.getPrimaryPrincipal())).getUserid();
       SimpleAuthorizationInfo simpleAuthorizationInfo=null;
       try {
           simpleAuthorizationInfo = new SimpleAuthorizationInfo();
           //1.设置所有的权限(注意权限是以字符串的形式保存的权限码)
           List<Permission> permissions1 = userService.selectPermissionsByUserId(userId);//获取所有权限码
           Set<String> permissions = new HashSet<>();
           for(Permission permission:permissions1){
               if(ValidateCheck.isNotNull(permission.getPermissioncode())){
                   permissions.add(permission.getPermissioncode());
               }
           }
           if (permissions != null && permissions.size()>0) {
               simpleAuthorizationInfo.setStringPermissions(permissions);
           }
           //2.设置角色,角色也是以字符串的形式表示(这里存的是角色名字)
           Set<String> userRoleNames = userService.getUserRoleNameByUserId(userId);
           if(userRoleNames != null && userRoleNames.size()>0){
               simpleAuthorizationInfo.setRoles(userRoleNames);
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
       return simpleAuthorizationInfo;
   }

}

获取用户信息

@RequestMapping("/first.action")
       public String first(Model model)throws Exception{
           
           //从shiro的session中取activeUser
           Subject subject = SecurityUtils.getSubject();
           //取身份信息
           User user = (User) subject.getPrincipal();
           //通过model传到页面
           model.addAttribute("activeUser", user);
           return "/first";
       }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值