Shiro - 受權

Authorization

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

 

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

咱們一般用三種元素進行受權操做,分別是:app

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

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

    一般狀況下,資源都支持CRUD操做。可是,每一種操做對一個資源有着不一樣的意義。
    因此咱們盡力將權限的粒度作的小一些。
    Permission僅僅聲明對某個資源能夠進行什麼樣的操做。
    好比:可否看到「刪除用戶」按鈕?可否瀏覽用戶列表頁面?this

  • Roles: Role能夠說是一系列動做的集合,一般將Role分配給User,User有沒有操做權限歸因於Role。
    Role有兩種類型,分別是隱式(implicity)和顯示(explicity)。編碼

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


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

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

    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對象。token

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

    @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對象集合。

相關文章
相關標籤/搜索