受权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操做等)。在受权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。数据库
主体
主体,即访问应用的用户,在 Shiro 中使用 Subject 表明该用户。用户只有受权后才容许访问相应的资源。编程
资源
在应用中用户能够访问的任何东西,好比访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要受权后才能访问。安全
权限
安全策略中的原子受权单位,经过权限咱们能够表示在应用中用户有没有操做某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面
查看/新增/修改/删除用户数据(即不少时候都是 CRUD(增查改删)式权限控制)
打印文档等等。。。app
如上能够看出,权限表明了用户有没有操做某个资源的权利,即反映在某个资源上的操做允不容许,不反映谁去执行这个操做。因此后续还须要把权限赋予给用户,即定义哪一个用户容许在某个资源上作什么操做(权限),Shiro 不会去作这件事情,而是由实现人员提供。ide
Shiro 支持粗粒度权限(如用户模块的全部权限)和细粒度权限(操做某个用户的权限,即实例级别的),后续部分介绍。post
角色
角色表明了操做集合,能够理解为权限的集合,通常状况下咱们会赋予用户角色而不是权限,即这样用户能够拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不一样的角色拥有一组不一样的权限。测试
隐式角色:
即直接经过角色来验证用户有没有操做权限,如在应用中 CTO、技术总监、开发工程师可使用打印机,假设某天不容许开发工程师使用打印机,此时须要从应用中删除相应代码;再如在应用中 CTO、技术总监能够查看用户、查看权限;忽然有一天不容许技术总监查看用户、查看权限了,须要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;若是进行修改可能形成多处代码修改。ui
显示角色:
在程序中经过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪一个角色不能访问某个资源,只须要从角色表明的权限集合中移除便可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。spa
基于角色的权限访问控制RBAC(role-based access control)是以角色为中心进行的访问控制,也就是判断主体subject是那个角色的方式进行权限访问控制,是粗粒度的code
基于资源的权限访问控制RBAC(resource-based access control)是以资源为中心进行的访问控制,只须要为角色添加权限就能够
区别:
因为基于角色的权限访问控制的角色与权限每每是多对多的关系(好比admin角色能够全部CURD的权限,部门经理角色有Retrieve权限,这就是多对多关系了),若是角色所对应的权限发生变化 ,那咱们所编写的判断逻辑就必须发生改变,可扩展性差
好比:本来只有admin能够访问,那么判断能够这么写 if(role.equals(”admin”)){ //retrieve }
可是假设后期须要给部门经理角色也赋予retrieve权限,那么必须改变原有代码,或者另外增长代码,总之要改变原有的判断逻辑
if(role.equals("admin") || role.equals("manager")){
//retrieve
}
若是是基于资源的权限访问控制,资源和权限一对一关系比较常见,不少时候资源和权限在数据库中会被合并在一张表中,只须要为资源分配相应的权限。因此一个具体操做对应的权限,只要直接判断用户是否拥有该权限便可,可扩展性强
//判断用户是否具备查看权限,用户的角色能够任意变化,而这条判断语句始终是可行的 if(user.hasPermission("retrieve")){ //retrieve } 若是用户的权限须要改变,只须要对数据库中用户的角色对应的权限进行改变,而权限与对应资源一般不会有改变的需求
受权方式:
Shiro 支持三种方式的受权:
编程式:经过写 if/else 受权代码块完成:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有权限 } else { //无权限 }
注解式:经过在执行的 Java 方法上放置相应的注解完成:
@RequiresRoles("admin") public void hello() { //有权限 }
没有权限将抛出相应的异常;
JSP/GSP 标签:在 JSP/GSP 页面经过相应的标签完成:
<shiro:hasRole name="admin"> <!— 有权限 —> </shiro:hasRole>
Demo:
基于角色的访问控制(隐式角色)
配置文件:
测试:
public class TestRole { @Test public void t1(){ Factory<SecurityManager> factory= new IniSecurityManagerFactory("classpath:shiro-role.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken("lc","123"); try { subject.login(token); System.out.println("用户是否拥有此角色:"+subject.hasRole("user")); System.out.println("用户是否拥有任意一个角色:"+subject.hasAllRoles(Arrays.asList("admin","root"))); System.out.println("用户受否拥有角色:"+Arrays.toString(subject.hasRoles(Arrays.asList("admin","root"))));
subject.checkRole("admin"); subject.checkRoles("admin","r"); System.out.println(); }catch (AuthenticationException e){ System.out.println("失败! "+e); } subject.logout(); } }
由配置文件知,lc用户只有admin与root两个角色,没有user角色,因此是false,其余两个方法相似;checkRole检查断言角色,admin能够,没有r角色因此报错。
基于资源的访问控制(显示角色)
配置文件:两个用户对应着本身的角色,角色对应着权限
root角色权限:select,update,insert,delete
admin角色权限:select,delete
user角色权限:select
测试:
由配置文件知cc用户角色为user,对应的权限为select,lc用户角色为root,admin,权限为select,update,insert,delete
@Test public void t2(){ Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("cc","123"); UsernamePasswordToken token2=new UsernamePasswordToken("lc","123"); try { subject.login(token); System.out.println(subject.isPermitted("select")); System.out.println( subject.isPermittedAll("select","insert","delete")); subject.login(token2); System.out.println(subject.isPermitted("select")); System.out.println( subject.isPermittedAll("select","insert","delete"));
subject.checkPermission("select");
subject.checkPermissions("select","insert");
}catch (AuthenticationException e){ System.out.println("nono"+e); } subject.logout(); }
结果:
Subject.isPermitted*/hasRole*
接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;isPermitted*/hasRole*
会返回 true,不然返回 false 表示受权失败。
经典权限系:
大体用到5张表:用户表(UserInfo)、角色表(RoleInfo)、菜单表(MenuInfo)、用户角色表(UserRole)、角色菜单表(RoleMenu)。
各表的大致表结构以下:
1、用户表(UserInfo):Id、UserName、UserPwd
2、角色表(RoleInfo):Id、RoleName
3、菜单表(MenuInfo):Id、MenuName
4、用户角色表(UserRole):Id、UserId、RoleId
5、角色菜单表(RoleMenu):Id、RoleId、MenuId
最关键的地方是,某个用户登陆时,如何查找该用户的菜单权限?其实一条语句便可搞定:
假如用户的用户名为zhangsan,则他的菜单权限查询以下:
Select m.Id,m.MenuName from MenuInfo m ,UserInfo u UserRole ur, RoleMenu rm Where m.Id = rm.MenuId and ur.RoleId = rm.RoleId and ur.UserId = u.Id and u.UserName = 'zhangsan'