Shiro - 授權


Authorization

說說權限的一些東東,不是Authentication,是Authorization。

 

簡單說就是access control即訪問控制,控制用戶對某個資源的訪問。
比如說,是否可以查看某個頁面、修改某個數據,甚至能不能看到某個按鈕。

我們通常用三種元素進行授權操作,分別是:

  • Permissions: 這個在Shiro中代表粒度(granularity)最小的(原子性)的安全策略。
    權限的粒度也可以再細化三個等級:

    • 資源:比如我可以對用戶信息進行修改
    • 實例:我可以對用戶A的信息進行修改
    • 屬性:我可以對用戶A的信息中的姓名進行修改

    通常情況下,資源都支持CRUD操作。但是,每一種操作對一個資源有着不同的意義。
    所以我們盡力將權限的粒度做的小一些。
    Permission僅僅聲明對某個資源可以進行什么樣的操作。
    比如:能否看到“刪除用戶”按鈕?能否瀏覽用戶列表頁面?

  • Roles: Role可以說是一系列動作的集合,通常將Role分配給User,User有沒有操作權限歸因於Role。
    Role有兩種類型,分別是隱式(implicity)和顯示(explicity)。

    • Implicity Roles:根據某個角色判斷是否對資源有操作權限,粒度較粗。
    • Explicity Roles:關注是否有進行該操作的權限,角色只是聚合了權限,用戶擁有某角色。 也可以理解為基於角色的訪問權限控制與基於資源的訪問權限控制(總之我討厭這種咬文嚼字的感覺,但我們又有必要有效地表達出我們的想法)。
      一般更建議使用基於資源的訪問權限控制。
  • Users: 即操作的主體,和Subject是一樣的概念。
    可以根據角色或者權限決定是否允許用戶執行某個操作。
    當然,我們可以直接將權限分配給用戶,也可以將權限分配給角色再將角色分配給用戶。
    或者我們也可以根據具體的需求再加一個層級。


權限判斷主要有3方式,分別是:

  • 編碼方式:
    先說說基於角色的控制,相對基於資源的控制來得簡單些。 關鍵是最后兩行代碼:

    Subject currentUser = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("King","t;stmdtkg");
    currentUser.login(token);
    
    currentUser.hasRole("admin");
    currentUser.checkRole("admin");
    //用角色判斷主要是兩類方法,hasRole*和checkRole*,前者返回boolean,后者拋出異常。
    boolean hasRole(String roleIdentifier);
    boolean[] hasRoles(List<String> roleIdentifiers);
    boolean hasAllRoles(Collection<String> roleIdentifiers);
    
    void checkRole(String roleIdentifier) throws AuthorizationException;
    void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;
    void checkRoles(String... roleIdentifiers) throws AuthorizationException;
    

 

通常,基於資源的控制較基於角色的控制更加有效。
用資源判斷也是兩類方法,isPermitted和checkPermission,前者返回boolean,后者拋出異常。
但與基於角色的方法不同的是,基於資源的方法的參數除了String也可以是org.apache.shiro.authz.Permission類型。
Permission是一接口,自定義一個Permission只需要實現boolean implies(Permission p)。
如果想更准確地表達一個權限,或者想在權限執行時增加一些邏輯或訪問一些資源則可以用Permission對象。

  • 注解方式:
    在方法上面加上權限注解。

    @RequiresRoles({"admin","leader"})
    public void deleteUsers(){
        //...
    }
    

    解釋一下這五個annotation:

    • @RequiresAuthentication:訪問或者調用被注解的類或者方法時通過認證。
    • @RequiresGuest:需要從未通過認證且沒有被記住(Remember me)。
    • @RequiresPremissions:需要特定的權限。
    • @RequiresRoles:需要特定的角色。
    • @RequiresUser:需要已通過認證
  • 頁面標簽: 我們可以根據權限去影響頁面的顯示

    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    
    <shiro:hasPermission name="user!delete.do">
        <a href="###">刪除用戶</a>
    </shiro:hasPermission>
    

 

Authorization Sequence


圖轉自Shiro官網,但是這個步驟是在是太羅嗦了。

Step 1.

Subject實例(一般為DelegatingSubject)的權限驗證方法被調用(也就是hasRole,checkRole,isPermitted,checkPermission這一系列)。
DelegatingSubject:

public boolean isPermitted(String permission) {
    return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}

 

Step 2.

獲取所有的principals並將權限驗證的工作委托給securityManager。 AuthorizingSecurityManager:

public boolean isPermitted(PrincipalCollection principals, String permissionString) {
    return this.authorizer.isPermitted(principals, permissionString);
}

 

Step 3.

securityManager直接將工作委托給其內部的authorizer。

public AuthorizingSecurityManager() {
    super();
    this.authorizer = new ModularRealmAuthorizer();
}

默認為ModularRealmAuthorizer,ModularRealmAuthorizer支持與多個Realm進行交互。
ModularRealmAuthorizer循環檢查每一個Realm是否實現了Authorizer,檢查通過則調用該Realm的權限驗證方法。
ModularRealmAuthorizer:

public boolean isPermitted(PrincipalCollection principals, String permission) {
    assertRealmsConfigured();
    for (Realm realm : getRealms()) {
        if (!(realm instanceof Authorizer)) continue;
        if (((Authorizer) realm).isPermitted(principals, permission)) {
            return true;
        }
    }
    return false;
}

 

Step 4.

Realm的權限驗證方法被調用,從權限信息中獲取權限集合,循環調用其implies方法判斷是否擁有權限。

(如圖,部分Realm也實現了Authorizer。

另外AuthorizingRealm的constructor中

this.permissionResolver = new WildcardPermissionResolver();  

 

如果傳入的權限是以String形式表示,則需要一個resolvePermission的過程。
此處會用到PermissionResolver將字符串轉為Permission實例。
如果Realm的權限驗證方法出現異常,異常將作為AuthorizationException傳至Subject的caller。
而隨后的Realm的驗證方法都不會得到執行。
如果Realm的權限驗證方法返回boolean(比如hasRole或者isPermitted)並且其中一個返回true,剩余的Realm則全部短路。

public boolean isPermitted(PrincipalCollection principals, String permission) {
    Permission p = getPermissionResolver().resolvePermission(permission);
    return isPermitted(principals, p);
}

public boolean isPermitted(PrincipalCollection principals, Permission permission) {
    AuthorizationInfo info = getAuthorizationInfo(principals);
    return isPermitted(permission, info);
}

private boolean isPermitted(Permission permission, AuthorizationInfo info) {
    Collection<Permission> perms = getPermissions(info);
    if (perms != null && !perms.isEmpty()) {
        for (Permission perm : perms) {
            if (perm.implies(permission)) {
                return true;
            }
        }
    }
    return false;
}

 

PermissionResolver

順便說說這個PermissionResolver,主要用於將以String表示的權限轉為Permission實例。
如果想定義一個PermissionResolver,我們只需要實現一個方法。

public interface PermissionResolver {
/** * Resolves a Permission based on the given String representation. * * @param permissionString the String representation of a permission. * @return A Permission object that can be used internally to determine a subject's permissions. * @throws InvalidPermissionStringException * if the permission string is not valid for this resolver. */ Permission resolvePermission(String permissionString); }

上面說過AuthorizingRealm(注意他下面還跟着一大票Realm)中默認使用的PermissionResolver實例為WildcardPermissionResolver。
什么是WildcardPemission?

用String表述權限的時候,即使我用"看用戶列表頁"、"晚上跑樓梯"、"open a file"這種字符串來描述也是沒有問題的。
但是他沒有可以利用的規則,我們無法用某種規則去解釋他(當然,有些情況下可能不需要解釋)。
Shiro提供了更直觀有力的表述語法——WildcardPermission。

比如我對某個資源有某些操作權限。
舉個栗子,對用戶有查看權限

user:query

 

不僅有查看權限,還有增加、修改和刪除

user:query
user:edit
user:create
user:delete

 

也可以寫成

"user:query,edit,create,delete"

 

如果對某個資源有所有操作權限,則:

user:*

 

或者也可以對所有資源擁有查看權限:

\*:query

 

如果要表示僅對某資源的某實例有某權限,則

user:query:king

 

當然,"*"也適用於實例級別的權限

user:*:king

 

繼續說說PermissionResolver。
當以String表述權限時,多數AuthorizingRealm的實現都會先將其轉換為Permission實例后再進行權限檢查邏輯。
權限檢查並不是單純的字符串比較。基於Permission對象的權限檢查可以呈現更好的邏輯,比如wildcardPermission中如果包含"*"什么的就不是字符串比較那么簡單了。
因此,幾乎所有的Realm都需要將String轉為Permission對象。
在Realm進行權限驗證工作的上一層,也就是Authorizer中如果傳遞一個String表述的權限過來,Realm則使用PermissionResolver將其轉換為Permission並開始驗證工作。
所有做權限驗證的Realm都默認使用WildcardPermissionResovler實例。
可能我們有更厲害的權限String語法,而且想讓所有的Realm都支持這個語法。
這個時候我們可以自己定義一個PermissionResolver並將其設置為全局PermissionResolver(global PermissionResolver)。
比如在.ini配置文件中:

globalPermissionResolver = com.foo.bar.authz.MyPermissionResolver
securityManager.authorizer.permissionResolver = $globalPermissionResolver

 

如果想配置一個全局PermissionResolver,每一個被注入的Realm都需要實現PermissionResolverAware接口。
當然,如果是集成AuthorizingRealm就不用想這些了,因為...

public abstract class AuthorizingRealm extends AuthenticatingRealm
        implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware

 

當然,也可以使用下面的方法顯示地指定一個PermissionResolver。

public void setPermissionResolver(PermissionResolver permissionResolver) {
    this.permissionResolver = permissionResolver;
    applyPermissionResolverToRealms();
}

 

或者在.ini中...

permissionResolver = com.foo.bar.authz.MyPermissionResolver

realm = com.foo.bar.realm.MyCustomRealm
realm.permissionResolver = $permissionResolver

 

相應地,RolePermissionResolver也是同理,只不過PermissionResolver是解析為Permission對象,而RolePermissionResolver是將角色String解析為Permission對象集合。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM