作者:_liuxx cnblogs.com/liuyh/p/8027833.html 前後端分離模式下,所有的交互場景都變成了數據,傳統業務系統中的許可權控制方案在前端已經不再適用,因此引發了我對許可權的重新思考與設計。對於非前後端分離模式下的許可權思考,看這裡:通用數據許可權的思考與設計 許可權控制到底控制 ...
作者:_liuxx
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
所有業務組件的理念均是結合服務端介面進行組件的封裝,兼顧靈活性的同時保證更優的業務開發速度。
歡迎指正,提出不同的看法。
推薦閱讀(點擊即可跳轉閱讀)
2. 面試題內容聚合
3. 設計模式內容聚合
4. Mybatis內容聚合
5. 多線程內容聚合