前后端分离中的权限管理思路
在傳統的前后端不分的開發中,權限管理主要通過過濾器或者攔截器來進行(權限管理框架本身也是通過過濾器來實現功能),如果用戶不具備某一個角色或者某一個權限,則無法訪問某一個頁面。
但是在前后端分離中,頁面的跳轉通通交給前端去做,后端只提供數據,這種時候,權限管理不能再按照之前的思路來。
首先要明確一點,前端是展示給用戶看的,所有的菜單顯示或者隱藏目的不是為了實現權限管理,而是為了給用戶一個良好的體驗,不能依賴前端隱藏控件來實現權限管理,即數據安全不能依靠前端,就像普通的表單提交一樣,前端做數據校驗是為了提高效率,提高用戶體驗,后端才是真正的確保數據完整性。(例如前端注冊時傳來一個郵箱地址,前端已經校驗通過,但是后端還需要再校驗,因為只有后端的校驗才是真正的確保數據是合法的,前端只是為了提高用戶體驗,給用戶快速的響應一下,免得用戶把數據傳到后端再返回才知道錯誤)。
所以真正的數據安全是在后端實現的,后端在設計的過程中,就要確保每一個接口都是在滿足某種權限的基礎上才能訪問。也就是說,不怕將后端數據接口暴露出來,即使暴露出來,只要你沒有相應的角色,也是訪問不了的。
前端為了良好的用戶體驗,需要將用戶不能訪問的接口或者菜單隱藏起來。
有人說,如果用戶直接在地址欄輸入某一個頁面的路徑,怎么辦?此時如果沒有過任何額外的處理的話,用戶確實可以通過直接輸入某一個路徑進入到系統的某一個頁面中,但是,不用擔心數據泄露問題。因為沒有相關的角色,就無法訪問相關的接口,但是如果用戶非這樣操作,進入到一個空白的頁面,用戶體驗不好,此時我們可以使用Vue中的路由導航守衛,來監聽頁面跳轉,如果用戶想要去一個未獲授權的頁面,則直接在前置路由導航守衛將之攔截下來,重定向登錄頁,或者直接就停留在當前頁,不讓用戶跳轉。
總而言之一句話,前端的所有操作,都是為了提高用戶體驗,不是為了數據安全,真正的效驗要在后端來做。
后端中的權限管理,每個角色統一一種路徑。
在路由表中的index.js文件中添加權限如下:
在Home主頁讀取數據出來顯示的時候,當前登陸的用戶如果不具備路由表index.js中所要求的角色,就不顯示指定菜單
前言
隨著移動互聯網的發展,前端開發領域也越來越廣,前端早已經告別了切圖的時代,迎來了規模化,工程化的大前端時代。近幾年隨著react、angular、vue等前端框架的興起,前后端分離的架構迅速流行。但同時權限控制也帶來了問題。
前后端分離之后,雖然前端也會進行權限控制、但是都比較簡單。而且僅僅前端進行權限控制并不是真正意義的權限控制,用戶完全可以繞開前端控制直接向后端發起請求。
權限設計
迄今為止最為普及的權限設計模型是RBAC模型,基于角色的訪問控制(Role-Based Access Control)
市面上流行的Apache Shrio、Spring Security,都是基于此模型設計的。權限系統可以說是整個系統中最基礎,同時也可以很復雜的,在實際項目中,會遇到多個系統,多個用戶類型,多個使用場景,這就需要具體問題具體分析,但最核心的RBAC模型是不變的,我們可以在其基礎上進行擴展來滿足需求。
下面是簡單的用戶、角色、操作、機構數據庫表設計,基本滿足了大多數業務場景。
前端控制
用戶登錄成功會生成一個Token,其中會附帶一些基本的用戶信息,不建議附帶角色權限信息。用戶向后端發送請求都會附帶這個Token。
前端一般是菜單和按鈕控制,在用戶登錄認證成功之后,根據用戶ID實時獲取菜單信息并渲染。按鈕控制的話,情況比較復雜,如果要求不是很高可以一次性查詢出來,放入本地緩存,進行本地鑒權。
這里擼一個比較簡單的實現:
hasRole: function(roles){var roleNames = this.userInfo.roleNames;if(roleNames!=""){var role = roles.split(",");var array = roleNames.split(",");for(var i=0;i<role.length;i++){if(array.indexOf(role[i])>-1){return true;}}}return false; }按鈕控制:
<i-button type="primary" v-if="hasRole('admin')" icon="ios-cloud-download" @click="exportCashReports">導出</i-button>后端控制
前端雖然進行了控制,但這遠遠不夠,特殊用戶完全可以避開前端控制直接向后端發起請求,這時候如果后端沒有對請求進行安全校驗,很容易會把敏感數據暴露出去。
在單體架構時代,如果我使用了安全框架,我只需要一個注解,亦或者一個攔截配置就可以搞定。
比如這樣:
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean (SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);shiroFilterFactoryBean.setLoginUrl("/login.html");shiroFilterFactoryBean.setUnauthorizedUrl("/403");Map<String, Filter> filtersMap = new LinkedHashMap<>();filtersMap.put("kickout", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap);Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();/*** 靜態文件*/filterChainDefinitionMap.put("/css/**","anon");filterChainDefinitionMap.put("/images/**","anon");filterChainDefinitionMap.put("/js/**","anon");filterChainDefinitionMap.put("/file/**","anon");/*** 登錄*/filterChainDefinitionMap.put("/login.html","anon");filterChainDefinitionMap.put("/sys/logout","anon");filterChainDefinitionMap.put("/sys/login","anon");/*** 管理后臺*/filterChainDefinitionMap.put("/sys/**", "roles[admin]");filterChainDefinitionMap.put("/**", "kickout,authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean; }或者這樣:
/*** 角色列表*/@PostMapping("/list")@RequiresRoles(value="admin")public Result list(SysRole role){return sysRoleService.list(role);}前后端分離之后,后端并不會保存任何用戶信息,只能通過前端傳輸的token進行校驗,這里參考Shrio的實現思路,自己擼一個注解來實現權限校驗。
/*** 權限注解*/ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresRoles {/*** A single String role name or multiple comma-delimitted role names required in order for the method* invocation to be allowed.*/String[] value();/*** The logical operation for the permission check in case multiple roles are specified. AND is the default* @since 1.1.0*/Logical logical() default Logical.OR; }然后寫個攔截器對請求進行攔截,這里截取部分認證代碼:
/*** @Description* @Date 2020/6/12*/ @Component public class AuthUtils {@Autowiredprivate RedisUtils redisUtils;public boolean check(Object handler,String userId){HandlerMethod handlerMethod = (HandlerMethod) handler;Annotation permAnnotation= handlerMethod.getMethod().getAnnotation(RequiresPermissions.class);if(permAnnotation!=null) {String[] role = handlerMethod.getMethod().getAnnotation(RequiresPermissions.class).value();List<String> roleList = Arrays.asList(role);/*** 獲取用戶實際權限*/List<Object> list = redisUtils.lGet("perms:"+userId,0,-1);List<String> permissions = roleList.stream().filter(item -> list.contains(item)).collect(toList());if (permissions.size() == 0) {return false;}}else{Annotation roleAnnotation= handlerMethod.getMethod().getAnnotation(RequiresRoles.class);if(roleAnnotation!=null){String[] role = handlerMethod.getMethod().getAnnotation(RequiresRoles.class).value();List<String> roleList = Arrays.asList(role);/*** 獲取用戶實際角色*/List<Object> list = redisUtils.lGet("roles:"+userId,0,-1);List<String> roles = roleList.stream().filter(item -> list.contains(item)).collect(toList());if (roles.size() == 0) {return false;}}}return true;} }以上,在用戶登錄成功以后會保存用戶的角色信息到緩存,生產環境中可以根據業務需求的實時性,在讀取數據庫和讀取緩存上自行選擇。
小結
前后端開發不是什么銀彈,微服務也不是,不要盲目的選型跟風,也不要學習什么大廠經驗,更不要談什么技術債,適合自己團隊的才是最好的。
當然,項目不高大上一些,出去怎么吹逼,怎么擴展團隊規模,怎么升職加薪,所以我前面的都是廢話,該分還是得分!!!
總結
以上是生活随笔為你收集整理的前后端分离中的权限管理思路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式设计与开发实践随笔-1
- 下一篇: 设计思维要点-1