背景:使用SpringBoot + shiro + vue 搭建一個權限控制的系統html
問題: 遇到的一些可能感興趣的問題
一、doGetAuthorizationInfo不會被調用
二、自定義過濾器致使訪問資源認證出現混亂(過濾器失效)
三、使用shiro進行動態的權限配置
四、更改用戶角色,如何讓shiro知道並在下一次訪問能作出調整
五、先後端分離如何作到讓unauthorizedUrl和loginUrl能訪問到正確的路徑(交由vue前端控制)
六、定義一個過濾器進行受權校驗前端
若是是採用註解或者配置文件寫死那麼不會這麼糾結,若是是一個beginner而且沒學會跑就要飛,那麼確定在doGetAuthorizationInfo不調用吃過很大的苦頭,官網指出了有幾種途徑可讓doGetAuthorizationInfo執行,這裏須要動態權限,因此採用編碼的方式
vue
角色檢查java
//get the current Subject Subject currentUser = SecurityUtils.getSubject(); if (currentUser.hasRole("administrator")) { //show a special button } else { //don’t show the button?) }
權限檢查ios
If (currentUser.isPermitted(printPermission)) { //do one thing (show the print button?) } else { //don’t show the button? }
上面兩種hasRole/isPermitted都會觸發doGetAuthorizationInfo調用,可是這種觸發方式並非很好,由於多角色有相同的資源路徑,會屢次調用doGetAuthorizationInfo影響性能
具體參考文章Shiro受權連接,shiro動態權限spring
下面代碼重寫過濾器的時候須要特別注意要用new CustomRolesAuthorizationFilter() 這麼建立過濾器而不是使用@Bean,具體緣由很複雜,能夠看看這篇文章爲何這樣子 。
而後講講這篇文章沒有講到的內容,爲何會這樣? springboot使用ApplicationFilterChain管理過濾器,shiro的11個默認過濾器和自定義shiro過濾器(自定義過濾器能夠覆蓋默認過濾器,由於默認過濾器先加入Map緩存)是放在一個名爲shiroFilter的對象交由ApplicationFilterChain管理,若是給自定義過濾器加上@Bean,那麼springboot會將這些自定義的過濾器放到ApplicationFilterChain上管理。
有什麼問題? 簡單說就是shiroFilter對象執行完裏面的shiro過濾器之後(login/xx = anon驗證經過了)會繼續執行ApplicationFilterChain對象剩餘的過濾器,而自定義過濾器(重寫authc也就是FormAuthenticationFilter)恰好就是這些剩餘的過濾器中的一個,這時會這個login/xx又會調用自定義過濾器再驗證一遍,這致使受權不經過。apache
/** ProxiedFilterChain類doFilter方法能夠了解一下 做用執行完當前shiro過濾器後回到springboot的過濾器 */ public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.filters == null || this.filters.size() == this.index) { //we've reached the end of the wrapped chain, so invoke the original one: if (log.isTraceEnabled()) { log.trace("Invoking original filter chain."); } this.orig.doFilter(request, response); } else { if (log.isTraceEnabled()) { log.trace("Invoking wrapped filter at index [" + this.index + "]"); } this.filters.get(this.index++).doFilter(request, response, this); } }
/** 其實不用重寫ShiroFilterFactoryBean,下面一個bean瞭解一下 */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, ShiroService shiroService) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 設置無權限時跳轉的 url; shiroFilterFactoryBean.setUnauthorizedUrl("/login/unAuthor.do"); // 重寫roles攔截規則 path = roles['role,role2'] Map<String, Filter> extraFilter = shiroFilterFactoryBean.getFilters(); extraFilter.put("roles", new CustomRolesAuthorizationFilter()); extraFilter.put("authc", new CustomFormAuthenticationFilter()); shiroFilterFactoryBean.setFilters(extraFilter); // 加載攔截資源 shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroService.loadFilterChainDefinitions()); return shiroFilterFactoryBean; }
參考:shiro動態權限axios
寫個service交由spring管理,提供加載清除shiro緩存的受權內容的功能後端
public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean) { synchronized (this) { AbstractShiroFilter shiroFilter; try { shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject(); } catch (Exception e) { throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!"); } PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver(); DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager(); // 清空老的權限控制 manager.getFilterChains().clear(); shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions()); // 從新構建生成 Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap(); for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue().trim() .replace(" ", ""); manager.createChain(url, chainDefinition); } } }
最簡單作法就是指定一個能訪問後臺的路徑,好比 shiroFilterFactoryBean.setUnauthorizedUrl("/login/unAuthor.do");
,而後寫一個controller方法返回狀態碼,交給前端路由axios的攔截器進行攔截處理跳轉。固然這裏對登陸處理能夠重寫FormAuthenticationFilter,在這個類中進行狀態碼的返回緩存
public class CustomRolesAuthorizationFilter extends AuthorizationFilter { private HttpServletRequest request; @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { Subject subject = getSubject(request, response); CmsUserEntity user = (CmsUserEntity) subject.getPrincipals().getPrimaryPrincipal(); if (null == user) { return true; } String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { return true; } List<String> rolesList = Arrays.asList(rolesArray); UserBiz userBiz = SpringUtil.getBean(UserBiz.class); List<String> roleNames = userBiz.findRoleNameByUserId(user.getId()); Set<String> roleSet = new HashSet<>(roleNames); boolean disjoint = Collections.disjoint(roleSet, rolesList); return !disjoint; } }