经过前面的学习,我们了解了shiro中的认证流程,并且学会了如何通过自定义Realm实现应用程序的用户认证。在这篇文章当中,我们将学习shiro中的授权流程。
授权概述这里的授权指的是授予某一系统的某一用户访问受保护资源的权限,分为查询、修改、插入和删除几类。没有相关权限的用户将无法访问受保护资源,具有权限的用户只能在自己权限范围内操作受保护资源。
关键对象 主体(Subject)即指定的某一用户,这里的用户可以是浏览器、APP和第三方应用程序等。
资源(Resource)这里的资源包括资源本身和对资源的操作。资源本身具备资源类型和资源实例两个属性。用户信息就是一个资源类型,向南的具体信息就是用户信息的实例。资源操作主要由查询、修改、删除、添加等组成。
权限(Permission)权限即是主体操作资源所需要的许可。权限本身不存在,只是某一系统的受保护资源的访问标识。脱离了资源谈权限就没有了意义。
授权流程(访问控制流程) 授权流程分析 前提:访问主体已经通过认证,登录到系统中。
用户请求访问某一受保护资源
判断用户是否具备访问权限:
2.1 有,则执行步骤3
2.2 没有,拒绝用户访问,并返回相应的提示
用户访问资源
授权模型目前面向民用系统主流的授权模式主要有基于资源的的访问控制和基于角色的访问控制
基于角色的访问控制RBAC (Role-Based Access Control)
其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。
基于资源的访问控制RBAC(Resource-Based Access Control )
在基于角色的访问控制当中,一但用户的角色确定了,那么,其权限也就被固定下来了。也就是说具有相同角色的两个主体,他们的权限是一样的。没有办法做到动态地改变主体的权限。基于资源的访问控制就是为了解决这个问题。
权限字符串权限字符串由资源标识符、操作符、资源实例标识符和分隔符组成,格式:资源标识符:操作符:资源实例标识符 。其含义是:对指定的一个或多个资源实例具有指定的操作权限,当资源实例为任意个时,资源实例标识符用*表示。分隔符:也可以换成/等形式。操作符一般分为create、find、update、delete四类,当具备该资源的所有操作时,操作符也可以用*表示。当资源标识符、操作符、资源实例标识符都为*时,代表该用户具备该系统的所有资源访问权限。
一些例子对用户01的查询权限表示为:user:find:01
对用户具有查询权限表示为:user:find:*或者user:find
对用户01具有所有权限表示为:user:*:01
上面只是列出了一种权限字符串的设计方法,大家在实践当中可以根据自己的应用特点去设计自己的权限字符串。
shiro中的授权(访问控制)实现方式 编程式 Subject currentSubject = SecurityUtils.getSubject(); if (currentSubject.hasRole("admin")){ //有权限的操作 }else{ //无权限 } 注解式(常用) @RequiresRoles("admin") public boolean createUser(User user){ //有权限才能执行方法 } 标签式 //在jsp中 <shiro:hasRole> //有权限才展示 </shiro:hasRole> 注意 :在Thymeleaf中使用时需要额外的集成。
编程实现我们沿用上一篇文章当中的例子,来演示shiro授权部分的内容。文末附有本文例子的下载方式
基于角色的访问控制 改造自定义Realm获取角色信息 **自定义Realm对象 * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/10/4 11:00 */ public class MySqlRealm extends AuthorizingRealm { public MySqlRealm() { //设置凭证匹配器,修改为hash凭证匹配器 HashedCredentialsMatcher myCredentialsMatcher = new HashedCredentialsMatcher(); //设置算法 myCredentialsMatcher.setHashAlgorithmName("md5"); //散列次数 myCredentialsMatcher.setHashIterations(1024); this.setCredentialsMatcher(myCredentialsMatcher); } /**授权方法 * 对于授权方法,每次判断主体是否具备对应权限时都会调用 * 因此,这里应当做缓存 * 缓存会在后面与springboot整合时讲 * @author 赖柄沣 bingfengdev@aliyun.com * @date 2020-10-04 11:01:50 * @param principalCollection * @return org.apache.shiro.authz.AuthorizationInfo * @throws AuthenticationException * @version 1.0 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //1. 获取当前主体的主身份信息,即用户名 String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal(); //2. 根据主身份信息查询数据库,获取主体具备的权限(模拟) SimpleAuthorizationInfo authenticationInfo = null; if ("xiangbei".equals(primaryPrincipal)){ authenticationInfo = new SimpleAuthorizationInfo(); authenticationInfo.addRole("admin"); } return authenticationInfo; } /**认证 * @author 赖柄沣 bingfengdev@aliyun.com * @date 2020-10-04 11:01:50 * @param authenticationToken * @return org.apache.shiro.authz.AuthorizationInfo * @throws AuthenticationException * @version 1.0 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 1. 从token中获取用户名 String principal = (String) authenticationToken.getPrincipal(); //2. 根据用户名查询数据库并封装成authenticationinfo对象返回(模拟) if (principal == "xiangbei") { //四个参数分别是数据库中的账号、加密后的密码、盐值、realm名字 AuthenticationInfo authInfo = new SimpleAuthenticationInfo("xiangbei", "ff595c47b51b4cf70fddce090f68879e", ByteSource.Util.bytes("ee575f62-0dda-44f2-b75e-4efef795018f"), this.getName()); return authInfo; } return null; } } 进行测试 hasRole /**访问控制测试 * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/10/5 16:48 */ public class AuthzTest { private CurrentSystemAuthenticator authenticator; @Before public void init() { this.authenticator = new CurrentSystemAuthenticator(); //对于授权,只有主体通过认证后才能进行,所以需要先登录系统 this.authenticator.authenticate("xiangbei","123"); } @Test public void testHasRole(){ Subject subject = SecurityUtils.getSubject(); //主体具备某一角色即可访问 if (subject.hasRole("admin")){ System.out.println(subject.getPrincipal()+" 具有 admin 角色"); } } }