Shiro系列 | 《Shiro開發詳細教程》第三章:Shiro授權-上

前景回顧:

► 第一章:Shiro入門(點擊即可進入)

► 第二章:Shiro身份認證-上(點擊即可進入)

► 第二章:Shiro身份認證-下(點擊即可進入)

本文目錄:

► 第三章:Shiro授權-上

► 3.1 授權定義

► 3.2 授權方式

► 3.3 授權實現示例

下節預告

► 第三章:Shiro授權(預告)-下

► 3.4 Permission(預告)

► 3.5 授權流程(預告)

3.1 授權定義

授權:

也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。在授權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、權限(Permission)、角色(Role)。

主體(Subject):

即訪問應用的用戶,在 Shiro 中使用 Subject 代表該用戶。用戶只有授權後才允許訪問相應的資源。

資源(Resource):

在應用中用戶可以訪問的任何東西,比如訪問 JSP 頁面、查看/編輯某些數據、訪問某個業務方法、打印文本等等都是資源。用戶只要授權後才能訪問。

權限(Permission):

安全策略中的原子授權單位,通過權限我們可以表示在應用中用戶有沒有操作某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源,如: 訪問用戶列表頁面、查詢/新增/修改/刪除用戶數據(很多時候,都是CRUD式權限控制)、打印文檔等。

權限代表了用戶有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許,不反映誰去執行這個操作。所以後續還需要把權限賦予給用戶,即定義哪個用戶允許在某個資源上做什麼操作(權限),Shiro 不會去做這件事情,而是由實現人員提供。

Shiro 支持粗粒度權限(如用戶模塊的所有權限)和細粒度權限(操作某個用戶的權限,即實例級別的)。

角色(Role):

角色代表了操作集合,可以理解爲權限的集合,一般情況下我們會賦予用戶角色而不是權限,即這樣用戶可以擁有一組權限,賦予權限時比較方便。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的權限。

角色又分爲:隱示角色和顯示角色。

隱示角色:

即直接通過角色來驗證用戶有沒有操作權限,如在應用中 CTO、技術總監、開發工程師可以使用打印機,假設某天不允許開發工程師使用打印機,此時需要從應用中刪除相應代碼;再如在應用中 CTO、技術總監可以查看用戶、查看權限;突然有一天不允許技術總監查看用戶、查看權限了,需要在相關代碼中把技術總監角色從判斷邏輯中刪除掉;

粒度是以角色爲單位進行訪問控制的,粒度較粗;如果進行修改可能造成多處代碼修改。

顯示角色:

在程序中通過權限控制誰能訪問某個資源,角色聚合一組權限集合;這樣假設哪個角色不能訪問某個資源,只需要從角色代表的權限集合中移除即可;無須修改多處代碼;即粒度是以資源/實例爲單位的;粒度較細。

3.2 授權方式

Shiro支持三種授權方式:

  • 編程式:(通過寫if/else授權代碼完成)
  • 註解式:(通過在執行的Java方法上放置響應的註解完成)
  • JSP\GSP標籤式:(在JSP\GSP頁面上添加響應的標籤完成)

詳解:

1:編程式

Subject subject = SecurityUtils.getSubject(); 
if(subject.hasRole(“admin”)) { 
    //有權限 
} else { 
    //無權限 
}

2:註解式

@RequiresRoles("admin") 
public void hello() { 
    //有權限 
}

3:JSP\GSP標籤:

<shiro:hasRole name="admin"> 
    <!— 有權限 —> 
</shiro:hasRole>

3.3 授權實現示例

基於角色的訪問控制(隱示角色)

1:在ini配置文件中配置用戶擁有的角色(shiro-role.ini)

[users]
zhangsan=mima,role1,role2
sunwukong=mima,role1

規則:用戶名=密碼,角色1,角色2

注:如果需要在應用中判斷用戶是否有相應角色,就需要在相應的 Realm 中返回角色信息,也就是說 Shiro 不負責維護用戶-角色信息,需要應用提供,Shiro 只是提供相應的接口方便驗證。

2:增加測試用例,代碼如下:

/**
 * @Auther: likang
 * @Date: 2018/10/22 11:37
 * @Description:
 */
public class ShiroRoleTest {

    public static final Logger logger = LoggerFactory.getLogger(ShiroTest.class);

    private void login(String configFile) {
        //1、獲取SecurityManager工廠,此處使用Ini配置文件初始化SecurityManager
        Factory<org.apache.shiro.mgt.SecurityManager> factory =
                new IniSecurityManagerFactory(configFile);
        //2、得到SecurityManager實例 並綁定給SecurityUtils
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證)
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "mima");
        try {
            subject.login(token);
            logger.info("登錄成功");
        } catch (UnknownAccountException e) {
            //5:身份驗證失敗
            logger.info("用戶名錯誤或者不存在");
        } catch (IncorrectCredentialsException e){
            logger.info("密碼不匹配");
        } catch (LockedAccountException e){
            logger.info("用戶已被鎖定,請聯繫管理員");
        } catch (DisabledAccountException e){
            logger.info("用戶已被禁用,請聯繫管理員");
        } catch (ExcessiveAttemptsException e){
            logger.info("用戶登錄次數過多");
        } catch (AuthenticationException  e){
            logger.info("用戶登錄失敗,請聯繫管理員");
        }
    }
    @Test
    public void testRoleShiro(){
        login("classpath:shiro-role.ini");
        Subject subject = SecurityUtils.getSubject();
        //判斷用戶是否擁有角色:role1
        Assert.assertTrue(subject.hasRole("role1"));
        //判斷用戶是否擁有所有角色:role1,role2
        Assert.assertTrue(subject.hasAllRoles(Arrays.asList("role1", "role2")));
        //判斷用戶是否擁有某些角色:role1,role2
        boolean[] result = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
        Assert.assertEquals(true, result[0]);
        Assert.assertEquals(true, result[1]);
        Assert.assertEquals(false, result[2]);
    }
}

Shiro 提供了 hasRole/hasRole 用於判斷用戶是否擁有某個角色/某些權限;但是沒有提供如 hashAnyRole 用於判斷是否有某些權限中的某一個。

@Test(expected = UnauthorizedException.class)
public void testCheckRole() {
    login("classpath:shiro-role.ini");
    Subject subject = SecurityUtils.getSubject();
    //斷言擁有角色:role1
    subject.checkRole("role1");
    //斷言擁有角色:role1 and role3 失敗拋出異常
    subject.checkRoles("role1", "role3");
}

Shiro 提供的 checkRole/checkRoles 和 hasRole/hasAllRoles 不同的地方是它在判斷爲假的情況下會拋出 UnauthorizedException 異常。

到此基於角色的訪問控制(即隱式角色)就完成了,這種方式的缺點就是如果很多地方進行了角色判斷,但是有一天不需要了那麼就需要修改相應代碼把所有相關的地方進行刪除;這就是粗粒度造成的問題。

基於資源的訪問控制(顯示角色)

1:在 ini 配置文件配置用戶擁有的角色及角色-權限關係(shiro-permission.ini)

[users]
zhangsan=mima,role1,role2
sunwukong=mima,role1

[roles]
role1=user:create,user:update
role2=user:create,user:delet

規則:

用戶名=密碼,角色1,角色2

角色1=權限1,權限2,權限3

  • 即首先根據用戶名找到角色,然後根據角色再找到權限;
  • 即角色是權限集合;
  • Shiro 同樣不進行權限的維護,需要我們通過 Realm 返回相應的權限信息。只需要維護“用戶—角色”之間的關係即可。

2:增加測試用例,代碼如下:

@Test
public void testIsPermitted() {
    login("classpath:shiro-permission.ini");
    Subject subject = SecurityUtils.getSubject();
    //判斷擁有權限: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"));
}

Shiro 提供了 isPermitted 和 isPermittedAll 用於判斷用戶是否擁有某個權限或所有權限,也沒有提供如 isPermittedAny 用於判斷擁有某一個權限的接口。

@Test(expected = UnauthorizedException.class)
public void testCheckPermission () {
    login("classpath:shiro-permission.ini");
    Subject subject = SecurityUtils.getSubject();
    //斷言擁有權限:user:create
    subject.checkPermission("user:create");
    //斷言擁有權限:user:delete and user:update
    subject.checkPermissions("user:delete", "user:update");
    //斷言擁有權限:user:view 失敗拋出異常
    subject.checkPermissions("user:view");
}

失敗的情況下會拋出 UnauthorizedException 異常。

到此基於資源的訪問控制(顯示角色)就完成了,也可以叫基於權限的訪問控制,這種方式的一般規則是“資源標識符:操作”,即是資源級別的粒度;

這種方式的好處就是如果要修改基本都是一個資源級別的修改,不會對其他模塊代碼產生影響,粒度小。但是實現起來可能稍微複雜點,需要維護“用戶—角色,角色—權限(資源:操作)”之間的關係。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章