授权简介
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
shiro通过角色赋予主体对资源CRUD(增删改查),角色是一组权限的集合。shiro支持粗粒度权限(基于角色的访问控制)和细粒度权限(基于资源的访问控制)。Shiro 不负责维护用户-角色、角色-权限信息,需要应用提供,Shiro 只是提供相应的接口方便验证。
shiro支持三种方式的授权:
编程式:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
注解式:
@RequiresRoles("admin")
public void hello() {
//有权限
}
jsp标签:
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
基于角色的访问控制
ini配置
[users]
zhang=123,role1,role2
wang=123,role
封装工具类
public class BaseTest {
public void login(String configFile,String username,String password){
Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
subject.login(token);
}
public Subject subject(){
return SecurityUtils.getSubject();
}
@After
public void tearDown(){
ThreadContext.unbindSubject(); //从当前线程解绑
}
}
测试类
public class RoleTest extends BaseTest{
@Test
public void testHasRole() {
login("classpath:role.ini","zhang","123");
Assert.assertTrue(subject().hasRole("role1")); //断言拥有role1角色
Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1","role2"))); //断言拥有role1,role2角色
boolean[] result = subject().hasRoles(Arrays.asList("role1","role2","role3"));
Assert.assertEquals(true,result[0]);
Assert.assertEquals(true,result[1]);
Assert.assertEquals(true,result[2]);
}
@Test(expected = UnauthorizedException.class)
public void testCheckRole(){
login("classpath:role.ini","zhang","123");
subject().checkRole("role1");
subject().checkRoles("role1","role2");
}
}
shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判断为假的情况下会抛出UnauthorizedException异常。上述基于角色的访问控制的缺点是当需要修改资源的权限则要修改上述代码,这就是粗粒度造成的问题。
基于资源的访问权限
ini配置
[users]
zhang=123,role1,role2
wang=123,role1
[roles]
role1=user:create
role2=user:create,user:delete
测试
@Test
public void testIsPermission(){
login("classpath:role.ini","zhang","123");
//断言拥有权限:user:create
Assert.assertTrue(subject().isPermitted("user:create"));
//断言拥有权限:user:create和user:delete
Assert.assertTrue(subject().isPermittedAll("user:create","user:delete"));
//断言拥有权限:user:view 失败抛出异常
subject().checkPermission("user:view");
subject().checkPermissions("user:view","user:create");
}
基于资源的访问控制,也可以叫基于权限的访问控制,这种方式的一般规则是“资源标识符:操作”,即是资源级别的粒度,用户需要维护“用户——角色,角色——权限(资源:操作)”之间的关系。
Permission
字符串通配符权限
规则:“资源标识符:操作:对象实例 ID” 即对哪个资源的哪个实例可以进行什么操作。其默认支持通配符权限字符串,“:”表示资源/操作/实例的分割;“,”表示操作的分割;“*”表示任意资源/操作/实例。
1.单个资源单个权限
role=system:user:update
2.单个资源多个权限
role=system:user:update,system:user:delete
role=system:user:update,delete
3.单个资源全部权限
role = system:user:create,update,delete,view
role = system:user:*
role = system:user
4.所有资源全部权限
role = *:view //所有资源的“view”的所有权限,如果要判断权限"system:user:view",则role=::view
5.实例级别的权限
单个实例单个权限
role = user:view:1
单个实例多个权限
role = user:update,delete:1
所有实例单个权限
role = user:auth:*
所有实例所有权限
role = user:*:*
6.shiro对权限字符串缺失部分的处理
7.Permission的实例WildcardPermission
subject().isPermitted("menu:view:1");
等价于
subject().isPermitted(new WildcardPermission("menu:view:1"));
授权流程
1.当调用subject.isPermitted*/hasRole*
时,岂会委托个SecurityManager,而SecurityManager接着会委托给Authorizer;
2.Authorizer是真正的授权者,如果我们调用如isPermited(”user:view”)时,其会先通过PermissionResolver把字符串转换成相应的Permisson实例;
3.在进行授权之前,Authorizer会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
4.如果有多个Realm时,会委托给ModularRealmAuthorizer进行循环判断,匹配传入的角色/权限。
ModularRealmAuthorizer 进行多 Realm 匹配流程:
- 首先检查相应的 Realm 是否实现了实现了 Authorizer;
- 如果实现了 Authorizer,那么接着调用其相应的isPermitted*/hasRole* 接口进行匹配;
如果Realm 进行授权的话,应该继承 AuthorizingRealm,其流程是:
- 如果调用 hasRole*,则直接获取 AuthorizationInfo.getRoles() 与传入的角色比较即可;首先如果调用如isPermitted(“user:view”),首先通过 PermissionResolver将权限字符串转换成相应的Permission 实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;
- 通过 AuthorizationInfo.getObjectPermissions() 得到 Permission 实例集合;通过 AuthorizationInfo.getStringPermissions() 得到字符串集合并通过PermissionResolver 解析为 Permission实例;然后获取用户的角色,并通过RolePermissionResolver 解析角色对应的权限集合(默认没有实现,可以自己提供);
- 接着调用Permission.implies(Permission p) 逐个与传入的权限比较,如果有匹配的则返回 true,否则 false。
Authorizer、PermissionResolver及RolePermissionResolver
Authorizer 的职责是进行授权(访问控制),是 Shiro API 中授权核心的入口点,其提供了相应的角色/权限判断接口。SecurityManager 继承了 Authorizer 接口,且提供了 ModularRealmAuthorizer 用于多 Realm 时的授权匹配。PermissionResolver 用于解析权限字符串到 Permission 实例,而 RolePermissionResolver 用于根据角色解析相应的权限集合。
对于 ModularRealmAuthorizer,相应的 AuthorizingSecurityManager 会在初始化完成后自动将相应的 realm 设置进去,我们也可以通过调用其 setRealms() 方法进行设置,ModularRealmAuthorizer 的 permissionResolver,其会自动设置到相应的 Realm 上(实现了 PermissionResolverAware 接口),ModularRealmAuthorizer 的 rolePermissionResolver,其会自动设置到相应的 Realm 上(实现了 RolePermissionResolverAware 接口)。
ini配置
[main]
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
#自定义rolePermissionResolver,shiro没有实现
rolePermissionResolver=com.shiro.resolver.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
securityManager.authorizer=$authorizer
realm=com.shiro.realm.MyRealm3
securityManager.realms=$realm
设置 securityManager 的 realms 一定要放到最后,因为在调用 SecurityManager.setRealms 时会将 realms 设置给 authorizer,并为各个 Realm 设置 permissionResolver 和 rolePermissionResolver。另外,不能使用 IniSecurityManagerFactory 创建的 IniRealm,因为其初始化顺序的问题可能造成后续的初始化 Permission 造成影响。
自定义MyRolePermissionResolver
//此处的实现很简单,如果用户拥有 role1,那么就返回一个 “menu:*” 的权限。
public class MyRolePermissionResolver implements RolePermissionResolver {
@Override
public Collection<Permission> resolvePermissionsInRole(String roleString) {
if("role1".equals(roleString)) {
return Arrays.asList((Permission)new WildcardPermission("menu:*"));
}
return null;
}
}
自定义Realm
package com.shiro.realm;
public class MyRealm3 extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRole("role1");
authorizationInfo.addRole("role2");
authorizationInfo.addObjectPermission(new WildcardPermission("user1:*"));
authorizationInfo.addStringPermission("user2:*");
return authorizationInfo;
}
}