作者:liuxx 來源:www.cnblogs.com/liuyh/p/8027833.html
前后端分離模式下,所有的交互場景都變成了數據,傳統業務系統中的權限控制方案在前端已經不再適用,因此引發了我對權限的重新思考與設計。
權限控制到底控制的是什么?
在理解權限控制之前,需要明白兩個概念:資源和權限。什么是資源,對于一個系統來說,系統內部的所有信息都可以理解為這個系統的資源。頁面是資源、數據是資源、按鈕是資源、圖片是資源、甚至頁面上一條分割線也可理解為是這個系統的資源。
而權限就是訪問某個資源所需要的標識。無論系統的權限如何設計,在用戶登錄時,都可以計算得出用戶所擁有的權限標識集合,也就確定了該用戶能訪問哪些系統資源,這就是我理解的權限控制的本質。于是我們可以得出:權限控制是控制登錄用戶對于系統資源的訪問。
前后端分離模式下,前后端在權限控制中各自的職責是什么?
在弄清前后端在權限控制中各自的職責是什么之前,需要理解前后端各自在系統中的職責。這個還是很好理解:
- 服務端:提供數據接口。
- 前端:路由控制、頁面渲染。
由于前端負責與用戶交互,用戶所能操作的資源入口都是由前端進行控制,那么前端的權限控制就包括:
前端路由的權限控制,過濾非法請求,用戶只能訪問權限范圍內的頁面資源。
頁面內組件的權限控制,根據用戶的權限控制頁面組件的渲染。包括各種按鈕、表格、分割線等。
隨著前端組件化的快速發展,用戶所看到的一切均可理解為組件,頁面是個大組件,其內部由各個小組件拼湊而來,那么前端權限控制最終落地到對組件的權限控制。于是腦補了出了最優雅的權限組件使用方式:
<組件 permissionName='xxx' />
前端可以渲染出用戶權限范圍內的各種系統資源,但是不能保證數據接口的安全性,某些比較喜歡折騰的用戶完全可以越過前端的頁面訪問我們系統的數據接口,那么服務端的權限控制最終落地到對接口的權限驗證。
實現思路
引上文,系統的一切資源均可進行權限控制,實際上也可以做到,但在我們實際的操作過程中,往往不需要細化到分割線那種程度。這里以按鈕級權限控制為例做實現說明,如果有更細粒度的權限需求,此思路依然可行。
前端路由權限控制。用戶登錄時拿到用戶擁有的權限標識集合,在前端存儲。路由變化時,進行權限判斷,通過則渲染對應頁面組件,否則渲染403組件。示例代碼:
let hasPermission = permission.check(current.permissionName); <div className={styles.content}> {hasPermission ? children : <Exception type={403}/>} </div>
封裝bird-button權限按鈕組件,傳入按鈕所需權限名,內部進行權限判斷,通過則渲染按鈕。
<BirdButton permissionName={'sys'} type='primary'>測試按鈕</BirdButton>
服務端。服務端權限驗證很好理解。使用攔截器驗證當前請求的權限。代碼示例:
public class SsoAuthorizeInterceptor extends HandlerInterceptorAdapter { @Autowired private TicketHandler ticketHandler; @Autowired private SsoAuthorizeManager authorizeManager; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) return false; HandlerMethod handlerMethod = (HandlerMethod) handler; SsoAuthorize authorize = handlerMethod.getMethodAnnotation(SsoAuthorize.class); if (authorize != null) { TicketInfo ticketInfo = ticketHandler.getTicket(request); if (ticketInfo == null) { throw new UnAuthorizedException("用戶信息已失效."); } String[] requirePermissions = authorize.permissions(); if(requirePermissions.length==0)return true; boolean isCheckAll = authorize.isCheckAll(); UserPermissionChecker permissionChecker = authorizeManager.getUserPermissionChecker(); if(!permissionChecker.hasPermissions(ticketInfo.getUserId(),requirePermissions,isCheckAll)){ throw new ForbiddenException("用戶沒有當前操作的權限."); } } return true; } }
源碼地址
本博客涉及到的前端權限控制思路均已在:
https://github.com/liuxx001/bird-front
項目中實現,項目中除了按鈕級權限方案還提供了后臺業務系統開發中常用的數據組件,包括:
下拉選擇器:bird-selector。
https://github.com/liuxx001/bird-front/blob/master/doc/bird-selector.md
全自動數據表格:bird-grid。
https://github.com/liuxx001/bird-front/blob/master/doc/bird-grid.md
全自動樹表:bird-tree-grid。
https://github.com/liuxx001/bird-front/blob/master/doc/bird-tree-grid.md
數據樹:bird-tree。
https://github.com/liuxx001/bird-front/blob/master/doc/bird-tree.md
全自動表單:bird-form。
https://github.com/liuxx001/bird-front/blob/master/doc/bird-form.md
權限按鈕:bird-button。
https://github.com/liuxx001/bird-front/blob/master/doc/bird-button.md
所有業務組件的理念均是結合服務端接口進行組件的封裝,兼顧靈活性的同時保證更優的業務開發速度。
歡迎指正,提出不同的看法。