当前位置 博文首页 > 快乐的小三菊的博客:shiro授权(四)

    快乐的小三菊的博客:shiro授权(四)

    作者:[db:作者] 时间:2021-07-28 20:49

    一、背景

    ? ? ? ?我们可以使用 shiro 进行授权操作,下面粘贴的是?LoginController?的代码,模拟用户登录的请求操作:

    @Controller
    @Slf4j
    public class LoginController {
    
    	@RequestMapping("/login")
    	public String login(User user) {
    		if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
    			return "error";
    		}
    		//用户认证信息
    		Subject subject = SecurityUtils.getSubject();
    		UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
    				user.getUserName(),
    				user.getPassword());
    		usernamePasswordToken.setRememberMe(true);
    		try {
    			// 用户账号和密码进行验证
    			subject.login(usernamePasswordToken);
    			
    			// 进行授权校验
    			if(subject.hasRole("admin")) {
    				// todo
    			}else {
    				// todo
    			}
                if(subject.isPermitted("normalUser:query")) {
    				// todo
    			}else {
    				// todo
    			}
    		} catch (UnknownAccountException e) {
    			log.error("用户名不存在!", e);
    			return "error";
    		} catch (AuthenticationException e) {
    			log.error("账号或密码错误!", e);
    			return "error";
    		} catch (AuthorizationException e) {
    			log.error("没有权限!", e);
    			return "error";
    		}
    		return "shiro_index";
    	}
    }
    

    二、源码追踪分析??

    ? ? ? ?这里面有 hasRole()isPermitted() 两个方法,我们今天只对 hasRole() 方法来分析下,其实这两个方法底层调用的原理都是类似的。调用?subject.login() 方法之后,若用户输入的账号和密码没有任何问题,我们可以进行用户的角色判断或者是权限判断,说白了就是不同的角色所拥有的权限是不一样的,我们来看下底层的代码是如何实现的。

    ? ? ? ?我们进入源码查看下?hasRole()?方法底层是如何实现的,可以看到调用了?securityManager?安全管理器的 hasRole()?方法,其中?hasPrincipals() 方法是判断系统是否拥有主体用户,即系统是否有用户, getPrincipals() 方法是获取当前系统的主体用户

     public boolean hasRole(String roleIdentifier) {
            return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
        }

    ? ? ? ?我们进入源码查看下 securityManager.hasRole() 方法是如何实现的,我们可以发现,系统又调用了?getAuthorizationInfo() 方法。

    public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
            AuthorizationInfo info = getAuthorizationInfo(principal);
            return hasRole(roleIdentifier, info);
        }

    ? ? ? ?我们进入源码查看下 getAuthorizationInfo() 方法是如何实现的。由于是用户第一次登录,所以 getAvailableAuthorizationCache() 方法肯定返回 null 值,所以后面会调用?doGetAuthorizationInfo() 方法。

     protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
            if (principals == null) {
                return null;
            }
            AuthorizationInfo info = null;
            if (log.isTraceEnabled()) {
                log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
            }
            Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
            if (cache != null) {
                if (log.isTraceEnabled()) {
                    log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
                }
                Object key = getAuthorizationCacheKey(principals);
                info = cache.get(key);
                if (log.isTraceEnabled()) {
                    if (info == null) {
                        log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
                    } else {
                        log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
                    }
                }
            }
            if (info == null) {
                // Call template method if the info was not found in a cache
                // 主要看下这块的代码
                info = doGetAuthorizationInfo(principals);
                // If the info is not null and the cache has been created, then cache the authorization info.
                if (info != null && cache != null) {
                    if (log.isTraceEnabled()) {
                        log.trace("Caching authorization info for principals: [" + principals + "].");
                    }
                    Object key = getAuthorizationCacheKey(principals);
                    cache.put(key, info);
                }
            }
            return info;
        }

    ? ? ? ?我们来看下 doGetAuthorizationInfo() ?这个方法是如何实现的,我们发现其实现类有如下几个,其中?CustomRealm?就是我们自定义实现的。

    ? ? ? ?我们再来看一下,我们自定义的?CustomRealm?中的?doGetAuthorizationInfo()?代码?,这个方法就是需要查询数据库中的用户、角色和权限,最后封装成?SimpleAuthorizationInfo?对象再返回去。

    @Override
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    		// 获取登录用户名
    		String name = (String)principalCollection.getPrimaryPrincipal();
    		// 查询用户名称
    		User user = userService.selectByUserName(name);
    		// 添加角色和权限
    		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    		List<String> roleNameList = new ArrayList<>();
    		List<String> permissionNameList = new ArrayList<>();
    		
    		for (Role role : user.getRoles()) {
    			roleNameList.add(role.getRoleName());
    			for (Permission permission : role.getPermissions()) {
    				permissionNameList.add(role.getRoleName()+":"+permission.getPermissionName());
    			}
    		}
    		// 添加角色
    		simpleAuthorizationInfo.addRoles(roleNameList);
    		// 添加权限
    		simpleAuthorizationInfo.addStringPermissions(permissionNameList);
    		return simpleAuthorizationInfo;
    	}

    ? ? ? ?截至到目前为止,我们算是获取到了所有用户的角色和权限数据了,接下来就是看下 shiro 是怎么进行授权的,我们返回去再看下?AuthorizingRealm 类的?hasRole() 方法,我们可以看到,获取完信息之后会调用?hasRole() 方法。

    public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
            AuthorizationInfo info = getAuthorizationInfo(principal);
            return hasRole(roleIdentifier, info);
        }

    ? ? ? ?我们再来看一下 hasRole()? 方法是如何实现的,可以看见就是一个简单的 contains() 方法校验,看当前用户是否拥有判断条件中的角色。

     protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
            return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
        }

    2.3、授权判断方式

    ? ? ? ?shiro 支持三种方式的授权,编码实现注解实现以及 JSP?Taglig实现,下面分别介绍下

    2.3.1、方式一:编码实现

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

    if(subject.hasRole("admin")) {
        // 有权限
    }else {
        // 无权限
    }

    2.3.2、方式二:注解实现

    ? ? ? ?通过在执行的?Java?方法上放置相应的注解完成,没有权限将抛出相应的异常。

    @RequiresRoles("admin")
    public void hello() {
    	// 有权限
    }

    2.3.3、方式三:通过 JSP 标签实现

    ? ? ? ?在?JSP/GSP?页面通过相应的标签完成。这种方式需要在 jsp 页面引入 shiro 的标签,如下所示:

    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
    <shiro:hasRole name="admin">
        <!--有权限-->
    </shiro:hasRole>

    2.4、权限的规则设定Permissions

    规则:

    ???????资源标识符冒号(:)操作,冒号(:)对象实例 ID 即对哪个资源的哪个实例可以进行什么操作,其默认支持通配符权限字符串,冒号(:)表示资源/操作/实例的分割。

    ???????逗号(,)表示操作的分割。

    ???????星号(*)表示任意资源/操作/实例。

    多层次管理:

    ? ? ? ?例如:user:query,user:edit

    ???????冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分是权限被操作的领域(打印机),第二部分是被执行的操作。

    ???????多个值:每个部件能够保护多个值。因此,除了授予用户 user:queryuser:edit 权限外,也可以简单地授予它们一个:user:query,edit

    ???????还可以用 * 号代替所有的值,如:user.* ,也可以写:*:query,表示某个用户在所有的领域都有 query 的权限。

    2.5、Shiro 的 Permissions

    实例级访问控制,这种情况通常会使用三个部件:操作被付诸实施的实例。如:user:edit:manager

    ???????也可以使用通配符来定义,如:user:edit:*user:*:*user:*:manager

    ???????部分省略通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于 user:edit:*user 等价于 user:*:*

    ???????注意:通配符只能从字符串的结尾处省略部件,也就是说 user:edit 并不等价于?user:*:edit

    2.6、JSP的标签

    ? ? ? ?Shiro 提供了 JSTL 标签用于在 JSP 界面进行权限控制,如根据登录用户显示相应的页面按钮。下面简单的介绍几个标签。

    ? ? ? ?guest?标签:用户没有身份验证时显示相应信息,即游客访问信息。

    <shiro:guest>
    	欢迎游客访问,<a href="login.jsp">登录</a>
    </shiro:guest>

    ? ? ? ?user 标签:用户已经经过认证/记住我登录后显示相应的信息。

    <shiro:user>
    	欢迎[<shiro:principal/>]登录,<a href="logout">退出</a>
    </shiro:user>

    ? ? ? ?authenticated 标签:用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。

    <!--如果在 CustomRealm 类的 doGetAuthenticationInfo() 中
        SimpleAuthenticationInfo 的构造方法传入的是用户名,
        可以采用下面这种写法获取当前登录用户-->
    <shiro:authenticated>
    	用户[<shiro:principal/>]身份已验证通过
    </shiro:authenticated>
    
    
    <!--如果在 CustomRealm 类的 doGetAuthenticationInfo() 中 
        SimpleAuthenticationInfo 的构造方法传入的是对象,
        可以采用下面这种写法获取当前登录用户。相当于((User)Subject.getPrincipals()).getName()-->
    <shiro:authenticated>
    	用户[<shiro:principal property="userName"/>]身份已验证通过
    </shiro:authenticated>

    ? ? ? ?notAuthenticated 标签:用户未进行身份验证,即没有调用 Subject.login 进行登录,包括记住我自动登录的也属于未进行身份验证。

    <shiro:notAuthenticated>
    	未身份验证(包括记住我)
    </shiro:notAuthenticated>

    ? ? ? ?principal 标签:显示用户身份信息,默认调用 Subject.getPrincipal() 获取,即 Primary Principal

    <shiro:principal property="username"/>

    ? ? ? ?hasRole 标签:如果当前 Subject 有角色将显示 body 体内容。

    <shiro:hasRole name="admin">
    	用户[<shiro:principal/>]拥有角色admin
    </shiro:hasRole>

    ? ? ? ?hasAnyRoles 标签:如果当前 Subject 有任意一个角色(或者关系)将显示在 body 体内容。

    <shiro:hasAnyRoles name="admin,user">
    	用户[<shiro:principal/>]拥有角色admin或user</br>
    </shiro:hasAnyRoles>

    ? ? ? ?lacksRole 标签:如果当前 Subject 没有角色将显示 body 体内容。

    <shiro:lacksRole name="admin">
        用户[<shiro:principal/>] 没有角色admin </br>
    </shiro:lacksRole>

    ? ? ? ?hasPermission?标签:如果当前 Subject 有权限将显示 body 体内容。

    <shiro:hasPermission name="user:create">
        用户[<shiro:principal/>]拥有权限user:create </br>
    </shiro:hasPermission>

    ? ? ? ?lacksPermission?标签:如果当前 Subject 没有权限将显示 body 体内容。

    <shiro:lacksPermission name="org:create">
        用户[<shiro:principal/>]没有权限org:create </br>
    </shiro:lacksPermission>

    ?

    ?

    ?

    cs