3. 授权
1. shiro权限初始
Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即按钮级别的)。
授权过程主要了解几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
Subject
:主体,即访问应用的用户,Subject接口提供了很多方法,主要包括进行认证的登录登出方法、进行授权判断的方法和Session访问的方法。
Resource
: 用户想访问的东西。
Permission
:安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,例如,一个用户操作实体的CRUD权力。
Role
:角色可以理解为自定义权限的集合,达到不用用户的权限粗颗粒实现。举个例子,学生和教师就两个不同的角色,他们分别由读书,做作业,教书,改作业的权限。
2. shiro授权方式
Shiro 支持三种方式的授权:
2.1 编程的方式:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
2.2 注解的方式
@RequiresRoles("admin")
public void hello() {
//有权限
}
2.3 注入到JSP中
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
3. 权限判断
3.1 粗颗粒(判断角色)
-
编写ini文件(shiro-role.ini)
[users] zhang=123,role1,role2 #role1,role2 表示角色 wang=123,role1
-
编写base 基类,方便后面编写
public class BaseTest { /** * 模拟登录 * @param configFile 文件位置 * @param username 用户名 * @param password 密码 */ protected void login(String configFile, String username, String password) { IniSecurityManagerFactory factory = new IniSecurityManagerFactory(configFile); SecurityManager manager = factory.getInstance(); SecurityUtils.setSecurityManager(manager); Subject subject = subject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); subject.login(token); } /** * 获取Subject * @return */ public Subject subject() { return SecurityUtils.getSubject(); } }
-
判断角色:hasRole,hasAllRole
@Test public void testHasRole(){ login("classpath:shiro-role.ini", "zhang", "123"); //判断拥有角色:role1 Assert.assertTrue(subject().hasRole("role1")); Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1", "role2"))); boolean[] result = subject().hasRoles(Arrays.asList("role1", "role2", "role3")); for(int i = 0; i < result.length; i++){ System.out.println(result[i]); } }
-
断言拥有角色,如果未拥有角色,将报UnauthorizedException异常
@Test(expected = UnauthorizedException.class) public void testCheckRole() { login("classpath:shiro-role.ini", "zhang", "123"); //断言拥有角色:role1 subject().checkRole("role1"); //断言拥有角色:role1 and role3 失败抛出异常 subject().checkRoles("role1", "role3"); }
3.2 细颗粒(判断权限)
-
编写ini文件(shiro-permission.ini)
[users] zhang=123,role1,role2 wang=123,role1 [roles] role1=user:create,user:update role2=user:create,user:delete
-
判断是否有权限,
isPermitted
,isPermittedAll
public class PermissionTest extends BaseTest { @Test public void testPermission(){ login("classpath:shiro-permission.ini", "zhang", "123"); //判断拥有权限:user:create Assert.assertTrue(subject().isPermitted("user:create")); //判断拥有权限:user:update and user:delete Assert.assertTrue(subject().isPermittedAll("user:update", "user:delete")); //判断没有权限:user:view Assert.assertFalse(subject().isPermitted("user:view")); } }
-
断言权限,如果没有该权限,会抛出异常
UnauthorizedException
@Test(expected = UnauthorizedException.class) public void testCheckPermission () { login("classpath:shiro-permission.ini", "zhang", "123"); //断言拥有权限:user:create subject().checkPermission("user:create"); //断言拥有权限:user:delete and user:update subject().checkPermissions("user:delete", "user:update"); //断言拥有权限:user:view 失败抛出异常 subject().checkPermissions("user:view"); }
4. 字符串通配符权限
4.1 权限配置规则
规则:“资源标识符:操作:对象实例 ID” 即对哪个资源的哪个实例可以进行什么操作。其默认支持通配符权限字符串,“:”表示资源/操作/实例的分割;“,”表示操作的分割;“*”表示任意资源/操作/实例。
-
单个资源配置
# 用户拥有资源“system:user”的“update”权限。 role41=system:user:update
-
单个字段多个权限
role42=system:user:update,system:user:delete
代码判断
// 这种方式只能用一下方式来判断 subject().checkPermissions("system:user:update", "system:user:delete"); // 以这种方式来写就会识别不到 subject().checkPermissions("system:user:update,delete");
第二种写法:
role43="system:user:update,delete"
代码判断
// 以下两种写法都能识别 subject().checkPermissions("system:user:update", "system:user:delete"); subject().checkPermissions("system:user:update,delete");
-
单个资源全部权限
role51="system:user:create,update,delete,view"
代码判断
// 以下两种写法都能识别 subject().checkPermissions("system:user:create,delete,update:view"); subject().checkPermissions("system:user:*");
-
所以资源全部权限
role61=*:view
代码判断
subject().checkPermissions("user:view");
用户拥有所有资源的“view”所有权限。假设判断的权限是“"system:user:view”,那么需要“role5=::view”这样写才行。
5. 授权过程
流程如下:
- 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
- Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
- 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false表示授权失败。
ModularRealmAuthorizer进行多Realm匹配流程:
- 首先检查相应的Realm是否实现了实现了Authorizer;
- 如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;
- 如果有一个Realm匹配那么将返回true,否则返回false。
如果Realm进行授权的话,应该继承AuthorizingRealm,其流程是:
1.1 如果调用hasRole,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可;
1.2 首先如果调用如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;
2 通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色,并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);
3 接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false。
6. Authorizer、PermissionResolver及RolePermissionResolver
Authorizer的职责是进行授权(访问控制),是Shiro API中授权核心的入口点,其提供了相应的角色/权限判断接口,具体请参考其Javadoc。SecurityManager继承了Authorizer接口,且提供了ModularRealmAuthorizer用于多Realm时的授权匹配。
PermissionResolver用于解析权限字符串到Permission实例,而RolePermissionResolver用于根据角色解析相应的权限集合。
代码太多了,我不想写了,我是参考这个文章来学习的
他这篇文章有一个问题,就是在定义shiro-authorizer.ini
文件时,少写几行代码
#没有调用自己声明的Realm
#自定义realm 一定要放在securityManager.authorizer赋值之后(因为调用setRealms会将realms设置给authorizer,并给各个Realm设置permissionResolver和rolePermissionResolver)
fooRealm=realm.MyRealm
securityManager.realms=$fooRealm
然后就是他没有提供shiro-jdbc-authorizer.ini
[main]
#自定义authorizer
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
#自定义permissionResolver
#permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
permissionResolver=permission.BitAndWildPermissionResolver
authorizer.permissionResolver=$permissionResolver
#自定义rolePermissionResolver
rolePermissionResolver=permission.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
securityManager.authorizer=$authorizer
#自定义realm 一定要放在securityManager.authorizer赋值之后(因为调用setRealms会将realms设置给authorizer,并给各个Realm设置permissionResolver和rolePermissionResolver)
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
dataSource.password=root
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm
其他的你跟着上面的文章就可以了。