在一個用戶登陸後,即身份認證經過,只能證實該登陸身份是合法的,至於具體能訪問系統中的什麼資源,須要經過受權來控制。通常系統中都是經過用戶關聯角色、角色再關聯權限來實現判斷一個用戶是否有某資源的使用權限,Shiro也提供了相應的實現權限控制。java
Shiro中的權限控制也是經過Filter來實現的,在前面認證流程中講到,Shiro的DefaultFilterChainManager
類會建立Filter鏈,鏈中包含了Shiro一些默認Filter,也能夠添加自定義Filter,並且這些Filter都有名字,Shiro會根據Filter配置爲每個配置的URL匹配符建立一個Filter鏈。web
protected FilterChainManager createFilterChainManager() { // 建立DefaultFilterChainManager DefaultFilterChainManager manager = new DefaultFilterChainManager(); // 建立Shiro默認Filter,根據org.apache.shiro.web.filter.mgt.DefaultFilter建立 Map<String, Filter> defaultFilters = manager.getFilters(); //apply global settings if necessary: for (Filter filter : defaultFilters.values()) { // 設置相關Filter的loginUrl、successUrl、unauthorizedUrl屬性 applyGlobalPropertiesIfNecessary(filter); } // 獲取在Spring配置文件中配置的Filter Map<String, Filter> filters = getFilters(); if (!CollectionUtils.isEmpty(filters)) { for (Map.Entry<String, Filter> entry : filters.entrySet()) { String name = entry.getKey(); Filter filter = entry.getValue(); applyGlobalPropertiesIfNecessary(filter); if (filter instanceof Nameable) { ((Nameable) filter).setName(name); } // 將配置的Filter添加至鏈中,若是同名Filter已存在則覆蓋默認Filter manager.addFilter(name, filter, false); } } //build up the chains: Map<String, String> chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) { for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); // 爲配置的每個URL匹配建立FilterChain定義, // 這樣當訪問一個URL的時候,一旦該URL配置上則就知道該URL須要應用上哪些Filter // 因爲URL匹配符會配置多個,因此以第一個匹配上的爲準,因此越具體的匹配符應該配置在前面,越寬泛的匹配符配置在後面 manager.createChain(url, chainDefinition); } } return manager; }
下面咱們看來都有哪些默認Filter,在DefaultFilterChainManager
構造方法中調用addDefaultFilters
方法:數據庫
protected void addDefaultFilters(boolean init) { for (DefaultFilter defaultFilter : DefaultFilter.values()) { addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false); } } public enum DefaultFilter { anon(AnonymousFilter.class), authc(FormAuthenticationFilter.class), authcBasic(BasicHttpAuthenticationFilter.class), logout(LogoutFilter.class), noSessionCreation(NoSessionCreationFilter.class), perms(PermissionsAuthorizationFilter.class), port(PortFilter.class), rest(HttpMethodPermissionFilter.class), roles(RolesAuthorizationFilter.class), ssl(SslFilter.class), user(UserFilter.class); // 省略一些代碼... }
DefaultFilter
中的PermissionsAuthorizationFilter
與RolesAuthorizationFilter
就是用於權限控制的Filter,名稱分別爲perms
與roles
。PermissionsAuthorizationFilter
用於判斷用戶訪問某URL時是否有相應權限,RolesAuthorizationFilter
用於判斷用戶訪問某URL時是否有相應角色。apache
因爲Shiro中Filter繼承體系比較複雜,要想理解Shiro權限是如何控制的就必須先理解Filter的繼承體系,以及理解繼承體系中父類Filter的特色及做用。因爲Filter繼承體系龐大,下面只列出PermissionsAuthorizationFilter
與RolesAuthorizationFilter
的繼承關係。數組
下面對繼承關係中一些重要的Filter做簡要說明,具體的Filter詳細分析容後續再講。app
NameableFilter
:爲Filter添加名稱jsp
OncePerRequestFilter
:保證Filter在鏈中只被執行一次ide
AdviceFilter
:源碼分析
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { Exception exception = null; try { // 前置處理,若是返回false則再也不執行鏈中的後續Filter boolean continueChain = preHandle(request, response); if (log.isTraceEnabled()) { log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]"); } if (continueChain) { // 繼續執行鏈中的後續Filter executeChain(request, response, chain); } // 後置處理 postHandle(request, response); if (log.isTraceEnabled()) { log.trace("Successfully invoked postHandle method"); } } catch (Exception e) { exception = e; } finally { cleanup(request, response, exception); } }
PathMatchingFilter
:基於路徑匹配的Filter,重寫preHandle
方法post
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { // appliedPaths存儲該Filter須要被應用的URL路徑,例若有這樣一個配置:/user_add.jsp = perms["user:add"] // 那麼在PermissionsAuthorizationFilter.appliedPaths中就有一條key爲/user_add.jsp, value爲[user:add]數組 // value爲數組而不是字符串的緣由是權限能夠有多個 // 若是appliedPaths爲空則直接繼續執行Filter鏈 if (this.appliedPaths == null || this.appliedPaths.isEmpty()) { if (log.isTraceEnabled()) { log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately."); } return true; } for (String path : this.appliedPaths.keySet()) { // 若是匹配,則根據onPreHandle方法的返回值來肯定是否繼續執行Filter鏈 if (pathsMatch(path, request)) { log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path); Object config = this.appliedPaths.get(path); return isFilterChainContinued(request, response, path, config); } } //no path matched, allow the request to go through: return true; } @SuppressWarnings({"JavaDoc"}) private boolean isFilterChainContinued(ServletRequest request, ServletResponse response, String path, Object pathConfig) throws Exception { // 若是該Filter可用 if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2 // 省略一些代碼... return onPreHandle(request, response, pathConfig); } // 省略一些代碼... return true; }
AccessControlFilter
:實現onPreHandle
方法
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
根據isAccessAllowed
方法的返回值來肯定是否繼續執行Filter鏈,若是不執行Filter鏈,則還會執行onAccessDenied
方法。
AuthorizationFilter
:實現了onAccessDenied
方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { Subject subject = getSubject(request, response); // 若是沒有登陸則重定向至登陸頁面 if (subject.getPrincipal() == null) { saveRequestAndRedirectToLogin(request, response); } else { // 重定向至未受權頁面 String unauthorizedUrl = getUnauthorizedUrl(); //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit: if (StringUtils.hasText(unauthorizedUrl)) { WebUtils.issueRedirect(request, response, unauthorizedUrl); } else { // 若是未受權頁面未配置則發送401狀態碼 WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } } return false; }
PermissionsAuthorizationFilter
:實現了isAccessAllowed
方法
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); // 訪問時須要的權限 String[] perms = (String[]) mappedValue; // 調用Subject對象的isPermitted或isPermittedAll來判斷是否有權限 boolean isPermitted = true; if (perms != null && perms.length > 0) { if (perms.length == 1) { if (!subject.isPermitted(perms[0])) { isPermitted = false; } } else { if (!subject.isPermittedAll(perms)) { isPermitted = false; } } } return isPermitted; }public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); // 訪問時須要的權限 String[] perms = (String[]) mappedValue; // 調用Subject對象的isPermitted或isPermittedAll來判斷是否有權限 boolean isPermitted = true; if (perms != null && perms.length > 0) { if (perms.length == 1) { if (!subject.isPermitted(perms[0])) { isPermitted = false; } } else { if (!subject.isPermittedAll(perms)) { isPermitted = false; } } } return isPermitted; }
isPermitted
和isPermittedAll
最終都委託給了ModularRealmAuthorizer.isPermitted
與ModularRealmAuthorizer.isPermittedAll
方法,至於爲何爲會委託給ModularRealmAuthorizer
請參看:Shiro源碼分析----登陸流程。
下面以ModularRealmAuthorizer.isPermitted
爲例,分析一下是如何進行權限判斷的:
public boolean isPermitted(PrincipalCollection principals, String permission) { assertRealmsConfigured(); for (Realm realm : getRealms()) { if (!(realm instanceof Authorizer)) continue; // 調用Realm的isPermitted方法 if (((Authorizer) realm).isPermitted(principals, permission)) { return true; } } return false; }
在使用Shiro時,Realm對象包含了認證與受權信息,在實際應用時,通常都是存儲在數據庫中的,且Realm通常都會自定義實現。實現自定義的Realm時,通常繼承自org.apache.shiro.realm.AuthorizingRealm
類,下面是一個例子:
public class UserRealm extends AuthorizingRealm { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } // 從數據庫中獲取權限信息 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String)principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 從數據庫中查詢當前用戶所擁有的角色 authorizationInfo.setRoles(userService.findRoles(username)); // 從數據庫中查詢當前用戶所擁有的權限 authorizationInfo.setStringPermissions(userService.findPermissions(username)); return authorizationInfo; } // 從數據庫中獲取認證信息 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String)token.getPrincipal(); User user = userService.findByUsername(username); if(user == null) { throw new UnknownAccountException();//沒找到賬號 } if(Boolean.TRUE.equals(user.getLocked())) { throw new LockedAccountException(); //賬號鎖定 } //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,若是以爲人家的很差能夠自定義實現 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user.getUsername(), //用戶名 user.getPassword(), //密碼 ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; } }
AuthorizingRealm.isPermetted
方法中就是根據用戶所擁有的權限與訪問時須要的權限進行匹配,若是有權限則繼續執行Filter鏈,反之則重定向至配置的未受權頁面。
理解了PermissionsAuthorizationFilter
的判斷邏輯,那麼RolesAuthorizationFilter
的判斷邏輯就很容易理解了,由於其流程是同樣的,只是RolesAuthorizationFilter
是基於用戶角色進行判斷的。
RolesAuthorizationFilter
:實現了isAccessAllowed
方法:
@SuppressWarnings({"unchecked"}) public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); // 獲取訪問時須要的角色 String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //no roles specified, so nothing to check - allow access. return true; } Set<String> roles = CollectionUtils.asSet(rolesArray); // 委託給Subject.hasAllRoles方法 return subject.hasAllRoles(roles); }
同理,hasAllRoles方法,最終都委託給了ModularRealmAuthorizer.hasAllRoles方法
public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) { assertRealmsConfigured(); for (String roleIdentifier : roleIdentifiers) { if (!hasRole(principals, roleIdentifier)) { return false; } } return true; } public boolean hasRole(PrincipalCollection principals, String roleIdentifier) { assertRealmsConfigured(); for (Realm realm : getRealms()) { if (!(realm instanceof Authorizer)) continue; if (((Authorizer) realm).hasRole(principals, roleIdentifier)) { return true; } } return false; }
AuthorizingRealm.hasAllRoles
方法中就是根據用戶所擁有的角色與訪問時須要的角色進行匹配,若是有角色則繼續執行Filter鏈,反之則重定向至配置的未受權頁面。
至此,Shiro受權流程分析完畢,若有錯誤之處,敬請指正。
-------------------------------- END -------------------------------
及時獲取更多精彩文章,請關注公衆號《Java精講》。