shiro实现授权
授权,也叫做访问控制,即在应用中控制谁能访问哪些资源(如访问页面、编辑数据、页面操作等)。在授权中需要了解几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
授权的概念
主体:
主体,即访问应用的用户,在Shiro中使用Subject代表用户。用户只有授权后才允许访问相应的资源。
资源:
在应用中用户可以访问的任何东西都称为资源。用户只有授权后才能访问。
权限:
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权利。即权限表示在应用中用户能不能访问某个资源。
shiro支持粗颗粒度权限(如用户模块的所有权限)和细颗粒度权限(操作某个用户的权限,即实例级别的)。
角色:
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限。不同的角色拥有一组不同的权限。
隐式角色:
即直接通过角色来验证用户有没有操作权限。比如:在应用中班长和课代表可以使用打印机,但是某一天老师不允许课代表使用打印机了,我们就需要将应用中课代表操作打印机的权限代码删除。即粒度是以角色为单位进行访问控制的,粒度较粗。
显示角色:
在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设某个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无需修改多处代码;即粒度是以资源、实例为单位的,粒度较细。
授权流程
解释:
- 首先调用
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,其流程是:
- 如果调用
hasRole*
,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可 - 如果调用如
isPermitted("user:view")
,首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission; - 通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo.getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色,并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);
- 接着滴啊用Permission.implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则返回false.
授权方式
Shiro支持三种方式的授权:
编程式: 通过写if/else
授权代码块完成:
1 2 3 4 5 6 |
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("admin")){ //有权限 } else{ //无权限 } |
注解式: 通过在指定的Java方法上放置相应的注解完成:
1 2 3 4 |
@RequiresRoles("admin") public void hello(){ //有权限 } |
没有权限将抛出相应的异常。
JSP标签: 在JSP页面通过相应的标签完成:
1 2 3 |
<shiro:hasRole name="admin"> <!-- 有权限 --> </shiro:hasRole> |
实现授权
基于角色
基于角色的访问控制(隐式角色)
1、shiro-role.ini
1 2 |
[users] tycoding=123,role1,role2 |
补充
此处ini配置文件的规则:用户名=密码,角色1,角色2,...
。如果在需要在应用中判断用户是否拥有相应角色,就需要需要在相应的Realm中返回角色信息,也就是说Shiro不负责维护用户-角色信息,Shiro只是提供了相应的接口方便验证。
2、RoleTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@Test public void testHasRole() { //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager Factory factory = new IniSecurityManagerFactory("classpath:shiro-role.ini"); //2、得到SecurityManager实例,并绑定给SecurityUtils SecurityManager securityManager = (SecurityManager) factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3、得到Subject及创建用户名、密码身份的Token Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("tycoding", "123"); try { //4、登录,即身份验证 subject.login(token); //判断用户是否拥有角色:role1 System.out.println(subject.hasRole("role1")); //判断用户是否拥有角色:role1、role2 boolean[] check1 = subject.hasRoles(Arrays.asList("role1", "role2")); for (boolean b: check1) { System.out.println(b); } //判断用户是否拥有角色:role1、role2、role3 boolean[] check2 = subject.hasRoles(Arrays.asList("role1", "role2", "role3")); for (boolean b: check2) { System.out.println(b); } } catch (AuthenticationException e) { //5、身份验证失败 e.printStackTrace(); } //6、退出 subject.logout(); } |
3、打印结果
1 2 3 4 5 6 |
true true true true false |
4、总结
如上就是基于角色的访问控制(即隐式角色),这种方式的缺点如果很多地方都进行了角色的判断,但是某一天不需要了,就要把相关的代码删除掉;这就是粗颗粒度造成的问题。
基于资源
基于资源的访问控制(显示角色)
1、shiro-permission.ini
1 2 3 4 5 6 |
[users] tycoding=123,role1,role2 [roles] role1:user:create,user:update role2:user:create,user:delete |
补充
此处ini配置文件的规则:”用户名=密码,角色1,角色2” “角色=权限1,权限2”。即首先根据用户名找到角色,然后再根据角色找到权限;即角色是权限的集合;Shiro同样不进行权限的维护,需要我们通过Realm返回相应的权限信息。只需要维护”用户-角色”之间的关系即可。
2、RoleTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@Test public void testPermissionRole() { //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager Factory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini"); //2、得到SecurityManager实例,并绑定给SecurityUtils SecurityManager securityManager = (SecurityManager) factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3、得到Subject及创建用户名、密码身份的Token Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("tycoding", "123"); try { //4、登录,即身份验证 subject.login(token); //判断用户是否拥有权限:user:create System.out.println(subject.isPermitted("user:create")); //潘墩用户是否拥有权限:user:update和user:delete boolean[] check = subject.isPermitted("user:create", "user:delete"); for (boolean b: check) { System.out.println(b); } } catch (AuthenticationException e) { //5、身份验证失败 e.printStackTrace(); } //6、退出 subject.logout(); } |
3、打印结果:
1 2 3 4 |
true true true |
4、总结
如上,我们事先了基于资源的访问控制(显示角色)。这种方式的优势显而易见,主要体现角色是权限的集合,这种方式的规则主要是资源标识符:操作
,即是资源级别的粒度。如果我们需要更改某个角色的权限,只需要一个资源级别的修改,不会对其他模块代码产生影响,粒度小。需要维护用户--角色,角色--权限
之间的关系。