- 專案特點
- Shiro的認證和授權
系統安全一直是在系統開發中不可規避的問題,而許可權控制又跟系統安全密不可分,大到使用者的訪問,小到一個頁面的按鈕,都有可能涉及到許可權的控制。而renren-security便給我們提供了一套許可權系統開發的解決方案。
renren-security是”人人社群”社群開源的輕量級許可權管理系統。系統採用SprinBoot、Mybatis、Shiro框架進行開發,極低門檻,拿來即用,支援分散式部署、Quartz分散式叢集排程、部門管理、資料許可權、雲儲存等功能。
專案特點
- 靈活的許可權控制,可控制到頁面或按鈕,滿足絕大部分的許可權需求
- 完善的部門管理及資料許可權,透過註解實現資料許可權的控制
- 完善的XSS防範及指令碼過濾,徹底杜絕XSS攻擊
- 支援MySQL、Oracle、SQL Server、PostgreSQL等主流資料庫
系統結構的設計也比較清晰,由admin、api、common等幾個模組組成,每個模組實現的功能大體如下:
common:公共模組,以jar包的形式被其他模組所依賴。實現了一些工具類和公共功能。包含時間處理、分頁、Sql過濾、Xss過濾和Redis切麵定義、自定義異常處理等功能。
admin:管理系統模組,以war包形式獨立部署。基於前後端分離的思想,主要用來用來開發後臺管理系統。包含使用者管理、角色管理、部門管理、選單管理、定時任務、檔案上傳、API校驗,同時採用Redis進行資料快取,支援單機和叢集的部署。
api:API介面模組,以war包形式獨立部署。模組主要提供給前端UI呼叫的一些業務介面,實現了使用者註冊、登入、介面許可權認證和使用者資訊獲取。同時整合了swagger2實現了API介面檔案,方便了介面的查詢和除錯。
系統設計之初就特別註重安全性,基於Shiro在頁面和介面都實現了許可權校驗。
使用者登入時對使用者的賬號密碼進行驗證,獲取使用者的資訊和role許可權,頁面顯示的時候會根據使用者擁有的許可權顯示對應的狀態,介面請求的時候也會進行使用者許可權的校驗,資料儲存到資料庫時候還進行Sql和Xss的過濾,整個過程的核心思路是Shiro對使用者的認證和授權。具體流程如下圖:
Shiro的認證和授權
實現Shiro的認證和授權,需要自定義Realm繼承於AuthorizingRealm,同時重寫doGetAuthenticationInfo(認證)和doGetAuthorizationInfo(授權)這兩個方法。這裡對於系統與Shiro的整合就不再做多的說明。
使用者登入的時候,將使用者的賬號和密碼包裝成一個UsernamePasswordToken後,再呼叫login提交賬戶認證,shiro會自動呼叫我們重寫的doGetAuthenticationInfo方法。
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//提交認證
subject.login(token);
//Shiro進行認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
//獲取使用者資訊
SysUserEntity user = new SysUserEntity();
user.setUsername(token.getUsername());
user = sysUserDao.selectOne(user);
//賬號不存在
if(user == null) {
throw new UnknownAccountException("賬號或密碼不正確");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
return info;
}
如果認證成功,那麼在系統的任何地方透過SecurityUtils.getSubject()方法就可以獲取認證透過的資訊。我們也可以藉助它的這點特性,實現使用者的自動登入。
這裡需要補充一點,系統把許可權化成了一個個的標簽儲存在資料庫中,使用者的許可權中持有對應的標簽則表示擁有對應的操作許可權。而對於Shiro的授權,在doGetAuthorizationInfo中需要獲取使用者的所有許可權串列,透過許可權串列篩選出是否擁有操作許可權。
//Shiro進行授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//獲取認證時候新增到SimpleAuthenticationInfo中的實體
SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
//查詢使用者所有許可權
Set permsSet = new HashSet();
List permsList = sysUserDao.queryAllPerms(userId);
for(String perms : permsList){
if(StringUtils.isBlank(perms)){
continue;
}
permsSet.addAll(Arrays.asList(perms.trim().split(",")));
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
Shiro的授權是被動的,只有被相應的條件觸發才會進行使用者授權,方式有以下幾種:
1.作用於頁面。頁面裡如果遇到,Shiro會呼叫自定義Realm獲取許可權資訊,看”sys:del”是否在許可權資料中存在,存在則授權透過,不存在則拒絕訪問,可應用於對一些按鈕和標簽的特定開放。
<#if shiro.hasPermission("sys:add")>
<a class="btn btn-primary" @click="add">新增a>
#if>
<#if shiro.hasPermission(“sys:del“)>
<a class=“btn btn-primary” @click=“del”>刪除a>
#if>
2.透過註解的方式作用於介面。在controller中,方法如果加了@RequiresPermissions(“sys:del”)註解,Shiro同樣會呼叫自定義Realm獲取許可權資訊,看”sys:del”是否在許可權資料中存在,存在則授權透過,不存在則拒絕訪問,從而實現對介面的許可權校驗。
@RequestMapping("/delete")
@RequiresPermissions("sys:del")
public R delete(long deptId){
//判斷是否有子部門
List deptList = sysDeptService.queryDetpIdList(deptId);
if(deptList.size() > 0){
return R.error("請先刪除子部門");
}
sysDeptService.deleteById(deptId);
return R.ok();
}
到此,基本上便實現了Shiro在頁面和介面的許可權控制。當然,Shiro更多是作用於表現層的一個控制,而出於系統安全考慮也應該增加對資料的校驗。因此在資料層面,則可透過Sql過濾和Xss過濾的方式實現過濾。專案中已經為其封裝成工具,原理都是正則匹配和字串替換,感興趣的夥伴可以直接到專案裡檢視,這裡就不再累述了。
系統除了實現許可權控制外,也實現了很多後臺管理系統開發中常用到的一些功能,像Quartz分散式叢集排程、多資料源動態切換以及叢集部署下Session管理,感興趣的夥伴也可以檢視原始碼。
專案地址:https://gitee.com/renrenio/renren-security