1. 程式人生 > >第三章:shiro授權認證

第三章:shiro授權認證

EDA classpath then ember utils 直接 測試 無權限 roles

授權:也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。

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

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

權限:安全策略中的原子授權單位,通過權限我們可以表示在應用中用戶有沒有操作某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源。

角色:代表了操作集合,可以理解為權限的集合,一般情況下我們會賦予用戶角色而不是權限,即這樣用戶可以擁有一組權限,賦予權限時比較方便。

隱式角色:即直接通過角色來驗證用戶有沒有操作權限,如在應用中CTO、技術總監、開發工程師可以使用打印機,假設某天不允許開發工程師使用打印機,此時需要從應用中刪除相應代碼;再如在應用中CTO、技術總監可以查看用戶、查看權限;突然有一天不允許技術總監查看用戶、查看權限了,需要在相關代碼中把技術總監角色從判斷邏輯中刪除掉;即粒度是以角色為單位進行訪問控制的,粒度較粗;如果進行修改可能造成多處代碼修改。

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

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>  

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

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

[users]  
zhang=123,role1,role2  
wang=123,role1   

規則即:“用戶名=密碼,角色1,角色2”,如果需要在應用中判斷用戶是否有相應角色,就需要在相應的Realm中返回角色信息,也就是說Shiro不負責維護用戶-角色信息,需要應用提供,Shiro只是提供相應的接口方便驗證,後續會介紹如何動態的獲取用戶角色。

2、測試用例

//Shiro提供了用於判斷用戶是否擁有某個角色的方法;
@Test
public void testHasRole() { login("classpath:shiro-role.ini", "zhang", "123"); //判斷擁有角色:role1 Assert.assertTrue(subject().hasRole("role1")); //判斷擁有角色:role1 and role2 Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1", "role2"))); //判斷擁有角色:role1 and role2 and !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]); } //Shiro也提供了用於斷言用戶是否擁有某個角色的方法; @Test public void testCheckRole() { login("classpath:shiro-role.ini", "zhang", "123"); //斷言擁有角色:role1 subject().checkRole("role1"); //斷言擁有角色:role1 and role3 失敗拋出異常 subject().checkRoles("role1", "role3"); }

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

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”“角色=權限1,權限2”,即首先根據用戶名找到角色,然後根據角色再找到權限;即角色是權限集合;Shiro同樣不進行權限的維護,需要我們通過Realm返回相應的權限信息。只需要維護“用戶——角色”之間的關系即可。

2、測試用例

//Shiro提供了用於判斷用戶是否擁有某個權限或所有權限
@Test  
public void testIsPermitted() {  
    login("classpath: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"));  
}   
//Shiro提供了用於斷言用戶是否擁有某個權限或所有權限
@Test(expected = UnauthorizedException.class)  
public void testCheckPermission () {  
    login("classpath:shiro-permission.ini", "zhang", "123");  
    //斷言擁有權限:user:create  
    subject().checkPermission("user:create");  
    //斷言擁有權限:user:delete and user:update  
    subject().checkPermissions("user:delete", "user:update");  
    //斷言擁有權限:user:view 失敗拋出異常  
    subject().checkPermissions("user:view");  
}  

字符串通配符權限

規則:“資源標識符:操作:對象實例ID” 即對哪個資源的哪個實例可以進行什麽操作。其默認支持通配符權限字符串,“:”表示資源/操作/實例的分割;“,”表示操作的分割;“*”表示任意資源/操作/實例。

1、單個資源單個權限

subject().checkPermissions("system:user:update");  

用戶擁有資源“system:user”的“update”權限。

2、單個資源多個權限

ini配置文件

role4=system:user:update,system:user:delete 

然後通過如下代碼判斷

subject().checkPermissions("system:user:update","system:user:delete");  

用戶擁有資源“system:user”的“update”和“delete”權限。如上可以簡寫成:

role4="system:user:update,delete"    

3、單個資源全部權限

ini配置文件

role51="system:user:create,update,delete,view"  

然後通過如下代碼判斷

subject().checkPermissions("system:user:create,delete,update:view");  

用戶擁有資源“system:user”的“create”、“update”、“delete”和“view”所有權限。如上可以簡寫成:

或者:role52=system:user:*  
或者:role53=system:user  

然後通過如下代碼判斷

subject().checkPermissions("system:user:*");  
subject().checkPermissions("system:user");   

4、所有資源全部權限

ini配置文件

role61=*:view  

然後通過如下代碼判斷

subject().checkPermissions("user:view");  

用戶擁有所有資源的“view”所有權限。假設判斷的權限是“"system:user:view”,那麽需要“role5=*:*:view”這樣寫才行。

5、實例級別的權限

5.1、單個實例單個權限

ini配置 文件

role71=user:view:1  

對資源user的1實例擁有view權限。

然後通過如下代碼判斷

subject().checkPermissions("user:view:1");  

5.2單個實例多個權限

ini配置 文件

role72=user:update,delete:1  

對資源user的1實例擁有update、delete權限。

然後通過如下代碼判斷

subject().checkPermissions("user:update,delete:1 ");
subject().checkPermissions("user:update:1,user:delete:1 ");

5.3單個實例所有權限

ini配置 文件

role71=user:*:1  

對資源user的1實例擁有view權限。

然後通過如下代碼判斷

subject().checkPermissions("user:view,update,delete,create:1 ");

5.4、所有實例單個權限

ini配置 文件

role74=user:auth:*  

對資源user的1實例擁有所有權限。

然後通過如下代碼判斷

subject().checkPermissions("user:auth:1", "user:auth:2"); 

5.5、所有實例所有權限

ini配置 文件

role74=user:*:*  

對資源user的1實例擁有所有權限。

然後通過如下代碼判斷

subject().checkPermissions("user:view:1", "user:auth:2");  

授權流程

技術分享圖片

流程如下:

1、首先調用Subject.isPermitted*/hasRole*接口,其會委托給SecurityManager,而SecurityManager接著會委托給Authorizer;

2、Authorizer是真正的授權者,如果我們調用如isPermitted(“user:view”),其首先會通過PermissionResolver把字符串轉換成相應的Permission實例;

3、在進行授權之前,其會調用相應的Realm獲取Subject相應的角色/權限用於匹配傳入的角色/權限;

4、Authorizer會判斷Realm的角色/權限是否和傳入的匹配,如果有多個Realm,會委托給ModularRealmAuthorizer進行循環判斷,如果匹配如isPermitted*/hasRole*會返回true,否則返回false表示授權失敗。

ModularRealmAuthorizer進行多Realm匹配流程:

1、首先檢查相應的Realm是否實現了實現了Authorizer;

2、如果實現了Authorizer,那麽接著調用其相應的isPermitted*/hasRole*接口進行匹配;

3、如果有一個Realm匹配那麽將返回true,否則返回false。

如果Realm進行授權的話,應該繼承AuthorizingRealm,重寫doGetAuthorizationInfo

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //1. 從 PrincipalCollection 中來獲取登錄用戶的信息
        Object principal = principals.getPrimaryPrincipal();
        //2. 利用登錄的用戶的信息來查詢當前用戶的角色或權限(可能需要查詢數據庫)
        Set<String> roles = new HashSet<String>();
        //3. 創建 SimpleAuthorizationInfo, 並設置其 reles 屬性.
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
        //4.設置當前用戶的權限
        Set<String> permissions = new HashSet<String>();
        info.setStringPermissions(permissions);
        //5.返回 SimpleAuthorizationInfo 對象. 
        return info;
    }

Shiro 常用註解:

@RequiresAuthentication

驗證用戶是否登錄,等同於方法subject.isAuthenticated() 結果為true時。

@RequiresUser

驗證用戶是否被記憶,user有兩種含義:

一種是成功登錄的(subject.isAuthenticated() 結果為true);

另外一種是被記憶的(subject.isRemembered()結果為true)。

@RequiresGuest

驗證是否是一個guest的請求,與@RequiresUser完全相反。

換言之,RequiresUser == !RequiresGuest

此時subject.getPrincipal() 結果為null.

@RequiresRoles

例如:@RequiresRoles("aRoleName");

如果subject中有aRoleName角色才可以訪問被修飾方法。

@RequiresPermissions

例如: @RequiresPermissions({"file:read", "write:aFile.txt"} )

要求subject中必須同時含有file:readwrite:aFile.txt的權限才能執行方法

Shiro 常用標簽

guest 標簽:用戶沒有身份驗證時顯示相應信息,即遊客訪問信息

技術分享圖片

user 標簽:用戶已經經過認證/記住我登錄後顯示相應的信息。

技術分享圖片

authenticated標簽:即Subject.login登錄成功,不是記住我登錄的

技術分享圖片

notAuthenticated 標簽:沒有調用Subject.login進行登錄,包括記住我自動登錄的

技術分享圖片

pincipal 標簽:顯示用戶身份信息,默認調用Subject.getPrincipal() 獲取

技術分享圖片

hasRole 標簽:如果當前 Subject 有角色將顯示 body 體內容:

技術分享圖片

hasAnyRoles 標簽:如果當前Subject有任意一個角色將顯示body體內容

技術分享圖片

lacksRole:如果當前 Subject 沒有角色將顯示 body 體內容

技術分享圖片

hasPermission:如果當前 Subject 有權限將顯示 body 體內容

技術分享圖片

lacksPermission:如果當前Subject沒有權限將顯示body體內容

技術分享圖片

第三章:shiro授權認證