3.1 概述
授权,又称访问控制,即在应用中控制用户可以使用哪些资源(如访问页面、页面操作、数据操作等),覆盖以下几个概念:
(1) 主体(Subject)
即用户,Shiro 中使用 Subject 代表用户。
(2) 角色(Role)
代表权限的集合,可以将权限集合起来一次性赋予用户。
(3) 权限(Permission)
安全策略中的原子授权单位,权限表示操作某个资源的能力。
(4) 资源(Resource)
应用中可以访问或操作的资源、如页面、数据、业务方法等。
角色又分为:
隐式角色
直接通过角色验证用户是否有某资源的操作权限。如果需要取消该权限,需要将用户对应的角色去除,属于粗粒度的权限控制。
显式角色
通过角色绑定的权限集合验证用户是否有某资源的操作权限。如果需要取消该权限,只需要将该权限从角色绑定的权限集合中去除,不需要去除用户的角色,属于细粒度的权限控制。
3.2 授权方式
Shiro 支持三种授权方式:
(1) 编程式
通过写 if/else
授权代码块实现
(2) 注解式
通过在执行的 Java 方法上放置注解 @RequiresRoles("admin")
实现
(3) JSP/GSP 标签
通过在 JSP/GSP 页面添加标签 <shiro:hasRole>
实现
3.3 授权实现
(1) 基于角色的访问控制(隐式角色)
首先,在 ini 配置文件(shiro-role.ini)中配置用户拥有的角色。
[users]
Steve=001,role1,role2
Tony=002,role1
在 ini 配置文件中配置用户角色规则:
用户名=密码,角色1,角色2,…
如果需要在应用中判断用户是否具有相应角色,需要在相应的 Realm
中返回角色信息,Shiro 不负责维护用户和角色的信息,需要应用自己提供,Shiro 只提供相应接口实现验证。
其次,编写测试用例。
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.util.ThreadContext;
import org.junit.After;
import org.junit.Test;
public class ShiroTest {
@Test
public void testHasRole() {
login("classpath:shiro-role.ini", "Steve", "001");
assertTrue(subject().hasRole("role1"));
assertTrue(subject().hasRole("role2"));
boolean[] result = subject().hasRoles(Arrays.asList("role1", "role2", "role3"));
assertTrue(result[0]);
assertTrue(result[1]);
assertFalse(result[2]);
}
private void login(String configFile, String username, String password) {
// 1.获取SecurityManager工厂,使用ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
// 2.获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager实例绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 4.通过SecurityUtils获取Subject
Subject subject = SecurityUtils.getSubject();
// 5.创建用户名、密码身份验证token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 6.使用用户名、密码身份验证token进行身份认证,即登录
subject.login(token);
}
private Subject subject() {
return SecurityUtils.getSubject();
}
@After
public void tearDown() {
// 如果需要同时运行多个test方法,在每个方法退出时需要解除绑定到当前线程的Subject实例,否则会影响接下来的test方法的执行。
ThreadContext.unbindSubject();
}
}
Shiro 提供 boolean hasRole(String roleIdentifier)
和 boolean[] hasRoles(List<String> roleIdentifiers)
方法用于判断用户是否拥有某个角色。
Shiro 还提供了 void checkRole(String roleIdentifier) throws AuthorizationException
和 void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException
方法检查用户是否拥有某个角色,与 hasRole
和 hasRoles
方法的不同之处在于:如果检查出用户不具有某种角色,hasRole
和 hasRoles
方法会返回 false
,而 checkRole
和 checkRoles
方法会抛出 UnauthorizedException
异常。
测试用例(上个测试用例中其它方法不变,只需要将 testHasRole
方法替换为 testCheckRole
方法):
@Test(expected = UnauthorizedException.class)
public void testCheckRole() {
login("classpath:shiro-role.ini", "Steve", "001");
subject().checkRole("role1");
subject().checkRoles(Arrays.asList("role1", "role3"));
}
(2) 基于资源的访问控制(显式角色)
首先,在 ini 配置文件(shiro-permission.ini)中配置用户拥有的角色及角色、权限关系。
[users]
Steve=001,role1,role2
Tony=002,role1
[roles]
#对资源user拥有create和update权限
role1=user:create,user:update
#对资源user拥有create和delete权限
role2=user:create,user:delete
在 ini 配置文件中配置角色、权限关系的规则:
角色=权限1,权限2,…
配合之前介绍的配置用户角色规则:
用户名=密码,角色1,角色2,…
可知,先根据用户名和密码进行身份认证,通过后根据用户名找到角色,再根据角色找到对应的权限集合。
其次,编写测试用例。
@Test
public void testIsPermitted() {
login("classpath:shiro-permission.ini", "Steve", "001");
assertTrue(subject().isPermitted("user:create"));
assertTrue(subject().isPermittedAll("user:update", "user:delete"));
assertFalse(subject().isPermitted("user:view"));
}
Shiro 提供了 boolean isPermitted(String permission)
和 boolean isPermittedAll(String... permissions)
方法用于判断用户是否拥有某权限。
Shiro 还提供了 void checkPermission(String permission) throws AuthorizationException
和 void checkPermissions(String... permissions) throws AuthorizationException
方法检查用户是否拥有某权限,如果检查出用户不拥有目标权限,会抛出 UnauthorizedException
异常。
测试用例:
@Test(expected = UnauthorizedException.class)
public void testCheckPermission() {
login("classpath:shiro-permission.ini", "Steve", "001");
subject().checkPermission("user:create");
subject().checkPermissions("user:update", "user:delete");
subject().checkPermission("user:view");
}
3.4 字符串通配符权限
规则:资源标识符:操作:对象实例ID,即对哪个资源的哪个实例执行何种操作。其中:
- “:”用于分隔资源/操作/实例
- “,”用于分隔多个操作
- “*”用于表示任意资源、操作或实例
(1) 单个资源的单个权限
java:subject().checkPermissions("system:user:create");
资源 system:user
的 create
权限。
(2) 单个资源的多个权限
ini:role41=system:user:update,system:user:delete
java:subject().checkPermissions("system:user:update", "system:user:delete");
资源 system:user
的 update
和 delete
权限。
可以简写为:
ini:role42="system:user:update,delete"
java:subject().checkPermissions("system:user:update,delete");
注意:通过 "system:user:update,delete"
验证 "system:user:update", "system:user:delete"
是没问题的,但是反过来规则不成立。
(3) 单个资源的全部权限
(4) 所有资源的全部权限
(5) 实例级别的权限