Shiro实现授权

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、总结
如上,我们事先了基于资源的访问控制(显示角色)。这种方式的优势显而易见,主要体现角色是权限的集合,这种方式的规则主要是资源标识符:操作,即是资源级别的粒度。如果我们需要更改某个角色的权限,只需要一个资源级别的修改,不会对其他模块代码产生影响,粒度小。需要维护用户--角色,角色--权限之间的关系。


交流

如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

如果你觉得这篇文章帮助到了你,你可以帮作者买一杯果汁表示鼓励