序言
由於最近一直卡在權限控制這個坎上,原來設計的比較簡單的權限控制思路已經無法滿足比較復雜一些的場景,因此一直在探索一種在大部分場景下比較通用的權限模型。
首先,這里說明一下兩種RBAC權限模型分別是“基於角色的權限控制(Role-Based-Access-Control)”和“基於資源的權限控制(Resource-Based-Access-Control)”兩種模型,這兩種模型是Java最常見的權限控制的模型。它們之間的數據庫結構區別並沒有太大,甚至也可以一樣。都是以最基礎的五張表(用戶表、角色表、用戶-角色關系表、權限表、角色-權限關系表)組成,往后再復雜的業務再在這個基礎上進行拓展,例如加入用戶組、組織、模塊等等概念,可以參考一下一這篇文章:
https://blog.csdn.net/qiaqia609/article/details/38102091
下面就以Spring Boot + Shiro為載體來具體記錄一下。
1.准備數據庫

這里還是貼一下Shiro的配置文件:ShiroConfig.java
-
/**
-
* @author phw
-
* @date Created in 04-08-2018
-
* @description 基於RESTFul風格的Shiro配置
-
*/
-
@Slf4j
-
@Configuration
-
public
class ShiroConfig {
-
-
@Bean
-
public DefaultWebSecurityManager securityManager(MyShiroRealm myShiroRealm) {
-
DefaultWebSecurityManager securityManager =
new DefaultWebSecurityManager();
-
securityManager.setRealm(myShiroRealm);
-
-
/**
-
* 關閉shiro自帶的session管理
-
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
-
*/
-
DefaultSubjectDAO subjectDAO =
new DefaultSubjectDAO();
-
DefaultSessionStorageEvaluator evaluator =
new DefaultSessionStorageEvaluator();
-
evaluator.setSessionStorageEnabled(
false);
-
subjectDAO.setSessionStorageEvaluator(evaluator);
-
securityManager.setSubjectDAO(subjectDAO);
-
-
return securityManager;
-
}
-
-
@Bean
-
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
-
ShiroFilterFactoryBean filterFactoryBean =
new ShiroFilterFactoryBean();
-
//filterFactoryBean.setLoginUrl("/sign-in");
-
//添加自己的過濾器並取名jwt
-
Map<String, Filter> filterMap =
new HashMap<>();
-
filterMap.put(
"jwt",
new JWTFilter());
-
filterFactoryBean.setFilters(filterMap);
-
filterFactoryBean.setSecurityManager(securityManager);
-
filterFactoryBean.setUnauthorizedUrl(
"/401");
-
-
//自定義url規則
-
Map<String, String> filterRuleMap =
new LinkedHashMap<>();
-
// 訪問401和404頁面不通過我們的Filter
-
/*filterRuleMap.put("/401", "anon");
-
filterRuleMap.put("/403", "anon");
-
filterRuleMap.put("/404", "anon");
-
filterRuleMap.put("/sign-in", "anon");
-
filterRuleMap.put("/sign-up", "anon");
-
filterRuleMap.put("/sign-out", "logout");
-
// 所有請求通過自己的JWT Filter
-
filterRuleMap.put("/**", "jwt");*/
-
-
/*List<Permission> permissions = permissionMapper.selectAll();
-
for (Permission permission: permissions) {
-
filterRuleMap.put(permission.getUrl(), permission.getInit());
-
}
-
filterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
-
log.info("Shiro Filter Factory Bean inject successful...");*/
-
return filterFactoryBean;
-
}
-
-
@Bean
-
@DependsOn(
"lifecycleBeanPostProcessor")
-
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
-
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator =
new DefaultAdvisorAutoProxyCreator();
-
// 強制使用cglib,防止重復代理和可能引起代理出錯的問題
-
// https://zhuanlan.zhihu.com/p/29161098
-
advisorAutoProxyCreator.setProxyTargetClass(
true);
-
return advisorAutoProxyCreator;
-
}
-
-
@Bean
-
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
-
return
new LifecycleBeanPostProcessor();
-
}
-
-
@Bean
-
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
-
AuthorizationAttributeSourceAdvisor advisor =
new AuthorizationAttributeSourceAdvisor();
-
advisor.setSecurityManager(securityManager);
-
return advisor;
-
}
-
-
}
這里配置的是無狀態(stateless)的shiro配置,因為要用到RESTful協議,所以需要使用jwt來保證api的安全。所以這里需要關閉掉Shiro自帶的Session管理器,然后啟用Shiro注解,自定義URL規則會在稍后說到。
2.Role Based Access Control
首先,來說一下比較常見的基於角色的權限控制。
我們所說的角色,其實就是一系列權限的集合,里面指定了哪種角色可以做什么事情,而用戶也可以看作是一組角色的集合,所以我們在使用第一種rbac來進行權限控制的時候,一般是判斷一個用戶是否有某一個角色。我們用Shiro來講就是像下面這樣:
-
@RequiresRoles(
"admin")
-
//或者
-
if(SecurityUtils.getSubject().hasRole(
"admin")) {
-
//do some thing...
-
System.out.println(
"I'm admin.")
-
}
上面的代碼是很多文章集成shiro后成功的樣子,我當時也是這么做的,但是隨着后來權限管理的業務越來越復雜,這種簡單的或者說靜態的權限管理模型已經不適用了,舉個栗子:如果現在我要增加一個角色,名字叫teacher,讓它也能打印一點東西。那么要做的就是首先在數據庫添加角色名,然后修改權限代碼,改成:
@RequiresRoles(value = {"admin", "teacher"}, logical = Logical.OR)
或者類似。也就是說,每一次變更需求,都需要變更代碼,然后重新部署項目~~這顯然是不怎么符合我們預期要求的。
因此這種模型只適合對權限需求變動不大的場景了。
當然,優點就是非常簡單,開發起來使用起來非常方面,特別是配合上Shiro,一個注解就能搞定。
3.Resource Based Access Control
第二種基於資源的權限控制,是第一種的優化方案,我們還是來看看栗子:
-
@RequiresPermissions(
"user:add")
-
//或者
-
if(SecurityUtils.getSubject().hasPermission(
"user:add")) {
-
//do some thing...
-
System.out.println(
"user:add");
-
}
這種方式在一定程度上解決了上述第一種模型的問題,網上也有很多關於Resource Based Access Control的解釋,但是這里我用我自己的話來解釋一下,還是用上面的栗子,客戶要求“admin”和“teacher”都有權限去打印一些東西。於是,第二種就不需要再修改代碼了,我直接在前台給teacher授權“user:add”這個權限就可以了啊,因為這樣已經很明確的指定了執行這個方法需要什么權限,就是“user:add”權限,我現在不管你是什么角色,什么組織,什么模塊也好,只要你擁有這個“user:add”這個權限,那你就能執行這個方法。
但是,問題又來了。
如果現在需求變更,執行這個方法需要另外一個權限“user:edit”怎么辦?是不是又得像上面那樣,修改代碼,重新部署項目。
所以,我們還需要一種能夠完全動態控制權限的模型,不把任何權限或者角色寫死,直接在數據庫(前台)配置,每增加一個功能就注冊一個功能然后指定它的需要的權限,就算需求變更,也不需要重新部署項目,只需要在前台稍微修改配置一下就能達到目的。
太晚了,今天就寫到這里吧,預知后事如何,請聽下回.......分解........
原文地址:https://blog.csdn.net/qq_33698579/article/details/80159823