简介
- 授权,也叫控制访问。关键对象:主体(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第三章》