說說權限的一些東東,不是Authentication,是Authorization。apache
簡單說就是access control即訪問控制,控制用戶對某個資源的訪問。
好比說,是否能夠查看某個頁面、修改某個數據,甚至能不能看到某個按鈕。 安全
咱們一般用三種元素進行受權操做,分別是:app
Permissions: 這個在Shiro中表明粒度(granularity)最小的(原子性)的安全策略。
權限的粒度也能夠再細化三個等級:ui
一般狀況下,資源都支持CRUD操做。可是,每一種操做對一個資源有着不一樣的意義。
因此咱們盡力將權限的粒度作的小一些。
Permission僅僅聲明對某個資源能夠進行什麼樣的操做。
好比:可否看到「刪除用戶」按鈕?可否瀏覽用戶列表頁面?this
Roles: Role能夠說是一系列動做的集合,一般將Role分配給User,User有沒有操做權限歸因於Role。
Role有兩種類型,分別是隱式(implicity)和顯示(explicity)。編碼
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:
頁面標籤: 咱們能夠根據權限去影響頁面的顯示
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <shiro:hasPermission name="user!delete.do"> <a href="###">刪除用戶</a> </shiro:hasPermission>
圖轉自Shiro官網,可是這個步驟是在是太羅嗦了。
Subject實例(通常爲DelegatingSubject)的權限驗證方法被調用(也就是hasRole,checkRole,isPermitted,checkPermission這一系列)。
DelegatingSubject:
public boolean isPermitted(String permission) { return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission); }
獲取全部的principals並將權限驗證的工做委託給securityManager。 AuthorizingSecurityManager:
public boolean isPermitted(PrincipalCollection principals, String permissionString) { return this.authorizer.isPermitted(principals, permissionString); }
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; }
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,主要用於將以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對象集合。