說說權限的一些東東,不是Authentication,是Authorization。
簡單說就是access control即訪問控制,控制用戶對某個資源的訪問。比如說,是否可以查看某個頁面、修改某個數據,甚至能不能看到某個按鈕。
我們通常用三種元素進行授權操作,分別是:
Permissions:這個在Shiro中代表粒度(granularity)最小的(原子性)的安全策略。權限的粒度也可以再細化三個等級:
通常情況下,資源都支持CRUD操作。但是,每一種操作對一個資源有著不同的意義。所以我們盡力將權限的粒度做的小一些。Permission僅僅聲明對某個資源可以進行什么樣的操作。比如:能否看到“刪除用戶”按鈕?能否瀏覽用戶列表頁面?
Roles:Role可以說是一系列動作的集合,通常將Role分配給User,User有沒有操作權限歸因于Role。Role有兩種類型,分別是隱式(implicity)和顯示(explicity)。
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:
頁面標簽: 我們可以根據權限去影響頁面的顯示
<%@ 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);}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:queryuser:edituser:createuser: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.MyPermissionResolversecurityManager.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.MyPermissionResolverrealm = com.foo.bar.realm.MyCustomRealmrealm.permissionResolver = $permissionResolver相應地,RolePermissionResolver也是同理,只不過PermissionResolver是解析為Permission對象,而RolePermissionResolver是將角色String解析為Permission對象集合。
新聞熱點
疑難解答