第三章 授权(学习笔记)
授权需要了解到的几个对象:主体(Subject),资源(Resource),权限(Permission),角色(Role)
主体:即访问的用户
资源:如 html jsp 页面,查看编辑等操作
权限:表示在应用中用户能否访问某个权限
角色:权限的集合
隐式角色:通过角色验证用户有没有操作权限,
显示角色:在程序中通过权限控制谁能访问某个资源,比如说不让某角色访问某个权限,直接在角色代表的权限集合中删掉即可
3.1 授权
基于角色的访问控制(隐式角色 )
1、在ini配置文件配置用户拥有的角色(shiro-role.ini)
[users]
zhang=123,role1,role2
wang=123,role1
规则:用户名=密码,角色1,角色2
public void login(String ini,String username,String password){ //1.创建 SecurityManager 工厂 Factory<SecurityManager> factory = new IniSecurityManagerFactory(ini); //2.得到SecurityManager实例 SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3.得到Subject 创建用户名密码 身份验证Token Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username,password); //4.身份验证 subject.login(token); //用户是否登录 Assert.assertEquals(true, subject.isAuthenticated()); //5.退出 //subject.logout(); }
@Test public void testRole(){ login("classpath:shiro-role.ini","zhang","123"); Subject subject = SecurityUtils.getSubject(); //判断拥有角色 Assert.assertEquals(true,subject.hasRole("role1")); //判断拥有角色 Assert.assertEquals(true,subject.hasAllRoles(Arrays.asList("role1","role2"))); //判断有哪些角色 boolean [] result = subject.hasRoles(Arrays.asList("role1","role2","role3")); System.out.println(result[0]); System.out.println(result[1]); System.out.println(result[2]); //checkRole/checkRoles判断为假的情况下会抛出UnauthorizedException异常。 subject.checkRole("role1"); //断言拥有角色:role1 and role3 失败抛出异常 subject.checkRoles("role1", "role3"); }
checkRole/checkRoles 如果验证失败会抛出 UnauthorizedException异常
到此基于角色的访问控制(即隐式角色)就完成了,这种方式的缺点就是如果很多地方进行了角色判断,但是有一天不需要了那么就需要修改相应代码把所有相关的地方进行删除;这就是粗粒度造成的问题。
基于资源的访问控制(显示角色)
1、在ini配置文件配置用户拥有的角色及角色-权限关系(shiro-permission.ini)
[users] zhang=123,role1,role2 wang=123,role1 [roles] role1=user:create,user:update role2=user:create,user:delete
规则:角色=权限1,权限2
首先通过用户找角色,角色是一个权限的集合 ,我们直接维护用户和角色之间的关系即可
Assert.assertTrue(subject.isPermitted("user:create")); Assert.assertTrue(subject.isPermittedAll("user:create","user:delete")); //没有权限会抛出UnauthorizedException异常 subject.checkPermission("user:create"); subject.checkPermissions("user:create","user:delete");
3.2 Permission
字符串通配符权限
规则:资源标识符:操作:对象实例ID
如:user:update:1 代表可以修改ID为1的用户
role3=user:update,user:delete
多个权限的也可以这么写:
role31="user:update,delete"
subject.checkPermissions("user:update,delete");
单个资源所有权限
role4=system:user:*
*可以不加 如:role4=system:user
这两种都可以
subject.checkPermissions("system:user:update,delete,save");
subject.checkPermissions("system:user:*");
所有资源全部权限
role5=*:view
通过代码判断
subject.checkPermissions("user:view");
假设我们要判断:system:user:view 需要将role5=*:view 改为 *:*:view 这样才行
system:user:* 等价于 system:user , *:*:view 不等于 user:view
3.3 授权流程
我这里简单的画了下授权的流程
1.首先调用Subject.isPermitted*/hasRole* 接口,他会委托给securityManager ,接着在委托给Authorizer
securityManager.checkPermissions(getPrincipals(), permissions);
2.ModularRealmAuthorizer 循环Realm
3.realm.isPermitted 方法中 通过 PermissionResolver 获取 Permission 实例
4.WildcardPermission 中匹配字符串 在这之前呢会通过getAuthorizationInfo(principals)获取realm 中配置
5.逐个进行匹配,匹配成功返回true,否则返回false
说白了就是把你传进的字符串 user:view 分割开 放入List<Set> 然后在获取realm中用户所对应的权限分隔开逐个对比
3.4 Authorizer、PermissionResolver及RolePermissionResolver
Authorizer 负责授权(访问控制)
PermissionResolver 用于解析字符串得到Permission实例
RolePermissionResolver 用于根据角色解析相应的权限集合
自定义授权
1、ini配置(shiro-authorizer.ini)
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
#自定义字符串匹配类 permissionResolver=com.zls.shiro.BitAndWildPermissionResolver authorizer.permissionResolver=$permissionResolver #根据角色获取权限 rolePermissionResolver=com.zls.shiro.MyRolePermissionResolver authorizer.rolePermissionResolver=$rolePermissionResolver securityManager.authorizer=$authorizer #自定义realm 一定要放在securityManager.authorizer赋值之后 #(因为调用setRealms会将realms设置给authorizer,并给各个Realm设置permissionResolver和rolePermissionResolver) realm=com.zls.shiro.realm.MyRealm securityManager.realms=$realm
2、定义BitAndWildPermissionResolver及BitPermission
BitPermission用于实现位移方式的权限,如规则是:
权限字符串格式:+资源字符串+权限位+实例ID;以+开头中间通过+分割;
权限:0 表示所有权限;1 新增(二进制:0001)、2 修改(二进制:0010)、4 删除(二进制:0100)、8 查看(二进制:1000);
如 +user+10 表示对资源user拥有修改/查看权限。 10的二进制是 1010
这个方法与WildcardPermission作用是一样的
public class BitPermission implements Permission{
private String resourceIdentify; //资源 String instanceId; //实例 private int permissionBit; //权限 public BitPermission(String permissionString){ String [] array = permissionString.split("\\+"); if(array.length > 1){ resourceIdentify = array[1]; } if(StringUtils.isEmpty(resourceIdentify)){ resourceIdentify = "*"; } if(array.length > 2){ permissionBit = Integer.valueOf(array[2]); } if(array.length > 3){ instanceId = array[3]; } if(StringUtils.isEmpty(instanceId)){ instanceId = "*"; } } @Override public boolean implies(Permission p) { if(!(p instanceof BitPermission)){ return false; } BitPermission other = (BitPermission) p; if(!("*".equals(this.resourceIdentify) || this.resourceIdentify.equals(other.resourceIdentify))){ return false; } if(!(this.permissionBit == 0 || (this.permissionBit & other.permissionBit) != 0)){ return false; } if(!("*".equals(this.instanceId) || this.instanceId.equals(other.instanceId))){ return false; } return true; } }
用+号区分自定义授权
public class BitAndWildPermissionResolver implements PermissionResolver { @Override public Permission resolvePermission(String permissionString) { if(permissionString.startsWith("+")) { return new BitPermission(permissionString); } return new WildcardPermission(permissionString); } }
3、定义MyRolePermissionResolver
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; } }
如果用户拥有role1,那么就返回一个“menu:*”的权限。
4、自定义Realm
public class MyRealm extends AuthorizingRealm{ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRole("role1"); authorizationInfo.addRole("role2"); authorizationInfo.addObjectPermission(new BitPermission("+user+10")); authorizationInfo.addObjectPermission(new WildcardPermission("user1:*")); authorizationInfo.addStringPermission("+user2+10"); authorizationInfo.addStringPermission("user2:*"); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; //获取用户名 String username = upToken.getUsername(); String password = String.valueOf(upToken.getPassword()); if(!"zhang".equals(username)) { throw new UnknownAccountException(); //如果用户名错误 } if(!"123".equals(password)) { throw new IncorrectCredentialsException(); //如果密码错误 } //如果身份认证验证成功,返回一个AuthenticationInfo实现; return new SimpleAuthenticationInfo(username, password, getName()); } }
此次还要注意就是不能把我们自定义的如“+user1+10”配置到INI配置文件,即使有IniRealm完成,因为IniRealm在new完成后就会解析这些权限字符串
现在就可以来进行测试了
//判断拥有权限:user:create Assert.assertTrue(subject().isPermitted("user1:update")); Assert.assertTrue(subject().isPermitted("user2:update")); //通过二进制位的方式表示权限 Assert.assertTrue(subject().isPermitted("+user1+2"));//新增权限 Assert.assertTrue(subject().isPermitted("+user1+8"));//查看权限 Assert.assertTrue(subject().isPermitted("+user2+10"));//新增及查看
有的朋友可能不明白这里 为什么!=0 就返回true
if(!(this.permissionBit == 0 || (this.permissionBit & other.permissionBit) != 0)){ return false; }
& : 位运算符,打个比方 2 & 10
计算这个总共分三步
1:转二进制 0010 1010
2:比
0010
1010
当两位都是1的时候为1,否则就是0
结果就是 0010
3:在转二进制 0010 就是 2
这回大家懂了吧,只要大于0 就说明是有权限的