简介
- 授权,也叫控制访问。关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
- 角色:权限的集合,一般情况下我们会赋予用户角色而不是权限。
- Shiro支持粗粒度(角色级别访问控制)和细粒度(权限级别)权限。
- 隐式角色:直接通过角色来验证用户有没有操作权限,粒度较粗,是以角色为单位进行访问控制的。如果有一天不允许总监这一角色使用打印机了,需要在逻辑代码中将总监去掉,不允许他做的事越多,代码修改量越大。(基于角色的控制访问)
- 显式角色:通过权限来验证用户有没有操作权限,假设哪个角色不能访问某个资源,只需要从角色的权限集合中移除某个权限即可,无需多处修改代码。(基于资源的控制访问)
授权方式
- Shiro支持三种方式的授权:编程式、注解式和JSP/GSP标签。
- 编程式:写if/esle代码块;常用
Subject subject=SecurityUtils.getSubject();
if(subject.hasRole("admin")){
//有权限
}else{
//无权限
}
- 注解式:在方法上加注解(一般在controller上),没有权限将抛UnauthorizedException异常
@RequiresRoles({"admin","user"},logical=logical.AND)
public void hello() {
//有权限
}
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)
@RequiresAuthentication //必须通过login登录
@RequiresUser //必须已登录(身份验证或记住我)
@RequiresGuest //未登录,游客
JSP/GSP标签:在JSP/GSP页面加标签
<shiro:hasRole name="admin"> <!— 有权限 —> </shiro:hasRole>
基于角色的访问控制(隐式角色)
- shiro-role.ini:配置用户拥有的角色
[users]
zhang=123,role1,role2
wang=123,role1
- BaseTest:抽象化一些常用测试方法
public abstract class BaseTest {
/**
* @Author haien
* @Description protected:只能被本类及其子类、本包的方法访问
* @Date 2019/2/18
* @Param [configFile, username, password]
* @return void
**/
protected void login(String configFile,String username,String password){
//1、获取SecurityManager工厂
Factory<SecurityManager> factory=new IniSecurityManagerFactory(configFile);
//2、得到SecurityManager实例,并绑定给SecurityUtils
SecurityManager securityManager=factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名、密码身份凭证Token
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
subject.login(token);
}
public Subject subject(){
return SecurityUtils.getSubject();
}
@After
public void tearDown() throws Exception{
ThreadContext.unbindSubject(); //退出时解除绑定Subject到线程,否则对下次测试造成影响
}
}
- RoleTest:测试Subject判断用户角色方法
public class RoleTest extends BaseTest{
@Test
public void testHasRole(){
//zhang拥有role1、role2
login("classpath:config/shiro-role.ini","zhang","123");
//判断是否拥有角色:role1
Assert.assertTrue(subject().hasRole("role1"));
//role1、role2
Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1","role2")));
//role1、role2和!role3
boolean[] result=subject().hasRoles(Arrays.asList("role1","role2","role3"));
Assert.assertEquals(true,result[0]);
Assert.assertEquals(true,result[1]);
Assert.assertEquals(false,result[2]);
}
@Test(expected = UnauthorizedException.class)
public void testCheckRole(){
login("classpath:config/shiro-role.ini","zhang","123");
//断言拥有角色role1
subject().checkRole("role1");
//断言拥有角色role1.role3,失败抛异常
subject().checkRoles("role1","role3");
}
}
- Subject并没有提供hasAnyRole()
- checkRole()和hasRole()的区别是它在判断为假的情况下会抛UnauthorizedException异常
基于资源的访问控制(显式角色)
- shiro-permission.ini
;首先根据用户名找到角色
[users]
zhang=123,role1,role2
wang=123,role1
;再根据角色找到权限
[roles]
;角色=权限1,权限2
role1=user:create,user:update
role2=user:create,user:delete
- PermissionTest:测试细粒度的授权
public class PermissionTest extends BaseTest{
@Test
public void testIsPermitted(){
login("classpath:config/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"));
}
@Test(expected = UnauthorizedException.class)
public void testCheckPermission(){
login("classpath:config/shiro-permission.ini","zhang","123");
//user:create
subject().checkPermission("user:create");
//user:create and user:update
subject().checkPermissions("user:create","user:update");
//!user:view,失败抛异常
subject().checkPermissions("user:view");
}
}
- 没有isPermittedAny()
权限书写规范
- 规则————资源标识符:操作:对象实例id,即对哪个资源的哪个实例可以进行什么操作。
- 支持通配符:“:”表示资源、操作和实例之间的分隔,“,”表示操作之间的分隔,“*”表示任意资源、操作或实例。
- 单个资源单个权限
//用户拥有system:user资源的update权限
subject().checkPermissions("system:user:update");
- 单个资源多个权限
role4=system:user:update,system:user:delete
//or
role4="system:user:update,delete"
//然后通过如下代码判断
subject().checkPermissions("system:user:update", "system:user:delete");
//or
subject().checkPermissions("system:user:update,delete");
- 以上四行代码随意搭配都可
- 单个资源全部权限
//推荐
role52=system:user:*
//or
role53=system:user
//然后通过以下代码判断
subject().checkPermissions("system:user:*");
subject().checkPermissions("system:user");
- 所有资源某一权限
//用户拥有所有资源的view权限
role61=*:view
subject().checkPermissions("user:view");
//假设权限是system:user:view,则写法为
role5=*:*:view
- 前缀匹配:前缀对了就能匹配任意特例
- user等价于user:*、user:*:*
- user:view等价于user:view:*
- 没有后缀匹配
- *:view不能匹配system:user:view
- *:*:view才能匹配system:user:view
- 性能问题:通配符陪陪方式比字符串相等匹配方式更复杂,需要花费更多时间。
WildcardPermission
- wildcard:通配符
- 以下两种方式是等价的
subject().checkPermission("menu:view:1");
subject().checkPermission(new WildcardPermission("menu:view:1"));
- 因此没什么必要的话使用字符串更方便。
流程解析
- 首先调用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;
- 是则调用相应的isPermitted/hasRole方法判断权限;
- 若有一个Realm匹配那么返回true,否则返回false。
用Realm进行授权应继承AuthorizingRealm,授权流程:
1.1. 如果调用hasRole*(role),则直接AuthorizationInfo.getRoles()获取用户角色与要求角色role对比即可;
2.1.1. 如果调用isPermitted(String),首先通过AuthorizationInfo.getStringPermissions()获得字符串集合,再通过PermissionResolver将权限字符串解析为Permission实例,默认使用WildcardPermissionResolver。
2.1.2. 如果通过isPermitted(WildcardPermission),则直接通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合即可;
2.2. 然后获取用户角色,通过RolePermissionResolver解析角色对应权限集合(无默认实现,可自己定义);
2.3. 调用Permission.implies(Permission p)逐个与传入的用户权限比较,若有匹配的则返回true,否则返回false。总结:Authorizer的职责是授权,提供了角色、权限判断接口。其子接口为SecurityManager,它的默认实现类为ModularRealmAuthorizer用于多Realm的授权匹配。PermissionResolver用于解析要求的权限字符串到Permission实例,RolePermissionResolver用于根据角色解析相应的权限集合。
指定Authorizer实现
//这里是默认实现,可以改为自定义的Authorizer实现类
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
securityManager.authorizer=$authorizer
- PS:自定义Authorizer可参考ModularRealmAuthorizer
- 指定Authorizer的permissionResolver(PermissionResolverAware接口实现类)
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
- 指定Authorizer的rolePermissionResolver(RolePermissionResolverAware接口实现类)
rolePermissionResolver=com.github.zhangkaitao.shiro.chapter3
.permission.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
- 代码实例:ideaProjects/shiroHelloWorld
- 《跟我学shiro第三章》