關於 Shiro 的權限匹配器和過濾器

項目源碼:https://github.com/weimingge14/Shiro-project
演示地址:http://liweiblog.duapp.com/Shiro-project/loginjava

關於 Shiro 的權限匹配器和過濾器git

上一節,咱們實現了自定義的 Realm,方式是繼承 AuthorizingRealm這個抽象類,分別實現認證的方法和受權的方法。程序員

這一節實現的代碼的執行順序: 
一、Shiro定義的過濾器和自定義的過濾器,在自定義的過濾器中執行 Subject對象的判斷是否具備某項權限的方法 isPermitted(),傳入某一個跟當前登陸對象相關的特徵值(這裏是登陸對象正在訪問的 url 鏈接) 
二、程序走到自定義的 Realm 中的受權方法中,根據已經認證過的主體查詢該主體具備的角色和權限字符串,一般狀況下是一個角色的集合和一個權限的集合。github

此時咱們第 1 步有一個字符串,第 2 步有一個字符串的集合(權限的集合)。 
程序要幫咱們作的就是看第 1 步的字符串在不在第 2 步的字符串集合中。那麼這件事情是如何實現的呢?數據庫

三、此時程序檢測到配置文件中有聲明一個實現了 PermissionResolver 的類,這個時候程序就會到這個類中去查找所採用的權限匹配策略。 
四、到上一步返回的實現了 Permission 的類的對象中的 implies()方法中去進行判斷。若是第 2 步的權限字符串數量多於 1 個,這個 implies()就會執行屢次,直到該方法返回 true 爲止,第 1 步的 isPermitted() 纔會返回 true。瀏覽器

下面咱們來關注一下 [urls]這個節點下面的部分。markdown

[urls]
# 配置 url 與使用的過濾器之間的關係 /admin/**=authc,resourceCheckFilter /login=anon

其中app

/admin/**=authc,resourceCheckFilter

表示,當請求 /admin/** 的時候,會依次通過 (1)authc和 (2)resourceCheckFilter 這兩個過濾器。ide

過濾器在有些地方也叫攔截器,他們的意思是同樣的。this

(1)authc這個過濾器是 Shiro 自定義的認證過濾器,即到自定義 Realm 的認證方法裏面去按照指定的規則進行用戶名和密碼的匹配。 
DefaultFilter這個枚舉類裏面定義了多個自定義的過濾器,能夠直接使用。

(2)resourceCheckFilter 是一個自定義的過濾器,咱們來看看它的聲明:

[filters]
# 聲明一個自定義的過濾器 resourceCheckFilter = com.liwei.shiro.filter.ResourceCheckFilter # 爲上面聲明的自定義過濾器注入屬性值 resourceCheckFilter.errorUrl=/unAuthorization

 

實現:

public class ResourceCheckFilter extends AccessControlFilter { private String errorUrl; public String getErrorUrl() { return errorUrl; } public void setErrorUrl(String errorUrl) { this.errorUrl = errorUrl; } private static final Logger logger = LoggerFactory.getLogger(ResourceCheckFilter.class); /** * 表示是否容許訪問 ,若是容許訪問返回true,不然false; * @param servletRequest * @param servletResponse * @param o 表示寫在攔截器中括號裏面的字符串 mappedValue 就是 [urls] 配置中攔截器參數部分 * @return * @throws Exception */ @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { Subject subject = getSubject(servletRequest,servletResponse); String url = getPathWithinApplication(servletRequest); logger.debug("當前用戶正在訪問的 url => " + url); return subject.isPermitted(url); } /** * onAccessDenied:表示當訪問拒絕時是否已經處理了;若是返回 true 表示須要繼續處理;若是返回 false 表示該攔截器實例已經處理了,將直接返回便可。 * @param servletRequest * @param servletResponse * @return * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { logger.debug("當 isAccessAllowed 返回 false 的時候,纔會執行 method onAccessDenied "); HttpServletRequest request =(HttpServletRequest) servletRequest; HttpServletResponse response =(HttpServletResponse) servletResponse; response.sendRedirect(request.getContextPath() + this.errorUrl); // 返回 false 表示已經處理,例如頁面跳轉啥的,表示不在走如下的攔截器了(若是還有配置的話) return false; } }

 

注意:咱們首先要關注 isAccessAllowed()方法,在這個方法中,若是返回 true,則表示「經過」,走到下一個過濾器。若是沒有下一個過濾器的話,表示具備了訪問某個資源的權限。若是返回 false,則會調用 onAccessDenied 方法,去實現相應的當過濾不經過的時候執行的操做,例如跳轉到某一個指定的登陸頁面,去引導用戶輸入另外一個具備更大權限的用戶名和密碼進行登陸。

isAccessAllowed()方法的最後一個參數 o,能夠得到咱們自定義的過濾器後面中括號中所帶的參數。

咱們再跳回到 isAccessAllowed()中:subject.isPermitted(url)。說明經過繼承 AccessControlFilter咱們能夠獲得認證主體 Subject和當前請求的 url 連接,它們的 API 分別是:

得到認證主體:

Subject subject = getSubject(servletRequest,servletResponse);

 

與 
得到當前請求的 url

String url = getPathWithinApplication(servletRequest);

 

而後,咱們調用了 subject.isPermitted(url)方法,將 url 這個字符串對象傳入。

此時咱們的流程應該走到 Realm的受權方法中,經過查詢(通過了認證的)用戶信息去查詢該用戶具備的權限信息。此時的代碼走到了這裏。 
在受權方法中,咱們看到 SimpleAuthorizationInfo的角色信息和權限信息都是經過字符串來解析的。 
角色信息和權限信息都是集合。

@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("--- MyRealm doGetAuthorizationInfo ---"); // 得到通過認證的主體信息 User user = (User)principalCollection.getPrimaryPrincipal(); // // 此處爲節約篇幅,突出重點省略 // SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(new HashSet<>(roleSnList)); info.setStringPermissions(new HashSet<>(resStrList)); // 以上完成了動態地對用戶受權 logger.debug("role => " + roleSnList); logger.debug("permission => " + resStrList); return info; }

 

在這個 Realm 的受權方法中,完成了對該認證後的主體所具備的角色和權限的查詢,而後放入 SimpleAuthorizationInfo對象中。

接下來就要進行 subject.isPermitted(url)中的 url 和 自定義 Realm 中的受權方法中的 info.setStringPermissions(new HashSet<>(resStrList));權限字符串集合的匹配操做了。

權限信息: 
這裏寫圖片描述
它們都是從數據庫中查詢出來的。

那麼如何實現匹配呢?比較簡單的一個思路就是比較字符串,可是這件簡單的比較的事情被 Shiro 定義爲一個 PermissionResolver,經過實現 PermissionResolver,咱們能夠爲完成自定義的權限匹配操做,能夠是簡單的字符串匹配,也能夠稍有靈活性的通配符匹配,這都取決於咱們程序員本身。

public class UrlPermissionResolver implements PermissionResolver { private static final Logger logger = LoggerFactory.getLogger(UrlPermissionResolver.class); /** * 通過調試發現 * subject.isPermitted(url) 中傳入的字符串 * 和自定義 Realm 中傳入的權限字符串集合都要通過這個 resolver * @param s * @return */ @Override public Permission resolvePermission(String s) { logger.debug("s => " + s); if(s.startsWith("/")){ return new UrlPermission(s); } return new WildcardPermission(s); } }

 

能夠看到,權限信息是經過字符串:「/admin/**」等來進行匹配的。這時就不能使用 Shiro 默認的權限匹配器 WildcardPermission了。

而 UrlPermission 是一個實現了 Permission接口的類,它的 implies 方法的實現決定了權限是否匹配,因此 implies 這個方法的實現是很重要的。

public class UrlPermission implements Permission { private static final Logger logger = LoggerFactory.getLogger(UrlPermission.class); // 在 Realm 的受權方法中,由數據庫查詢出來的權限字符串 private String url; public UrlPermission(String url){ this.url = url; } /** * 一個很重要的方法,用戶判斷 Realm 中設置的權限和從數據庫或者配置文件中傳遞進來的權限信息是否匹配 * 若是 Realm 的受權方法中,一個認證主體有多個權限,會進行遍歷,直到匹配成功爲止 * this.url 是在遍歷狀態中變化的 * * urlPermission.url 是從 subject.isPermitted(url) * 傳遞到 UrlPermissionResolver 中傳遞過來的,就一個固定值 * * @param permission * @return */ @Override public boolean implies(Permission permission) { if(!(permission instanceof UrlPermission)){ return false; } // UrlPermission urlPermission = (UrlPermission)permission; PatternMatcher patternMatcher = new AntPathMatcher(); logger.debug("this.url(來自數據庫中存放的通配符數據),在 Realm 的受權方法中注入的 => " + this.url); logger.debug("urlPermission.url(來自瀏覽器正在訪問的連接) => " + urlPermission.url); boolean matches = patternMatcher.matches(this.url,urlPermission.url); logger.debug("matches => " + matches); return matches; } }

 

重點說明:若是在自定義的 Realm 中的受權方法中傳入的受權信息中的權限信息是一個集合,那麼這裏的 implies 就會進行遍歷,直到這個方法返回 true 爲止,若是遍歷的過程所有返回 false,就說明該認證主體不具備訪問某個資源的權限。

相關文章
相關標籤/搜索