用戶權限表結構設計「用戶角色權限表設計」

背景說明

近期寫代碼又開始重新接觸了一點控台應用,接觸到的項目年代久遠,所有的權限管理用起來感覺不是很得心應手。

於是想着自己能否從零設計一個,梳理一下思路,當然實際用不用也無所謂。

權限管理主要是為了安全,項目中的權限管理是全部放在前端控制的,感覺這一點非常不安全。

前端防君子,不防小人。

如何從零開始設計權限管理系統

籠子.jpg

當然本次造輪子主要也是為了打造一款自己滿意的權限控制框架,所以設計採用 MVP 模式,採用漸進式的方式開發。

可以一起學習一下權限控制的設計和實現思路。

如果生產想直接使用,也推薦目前比較成熟的框架:

spring-security

Spring Security-01-Hello World

shiro

Shiro

權限設計的基礎知識

寫代碼之前,我們先學習一點權限設計的基礎知識。

權限設計發展至今,網上的資料還是比較豐富的,我們節選一些比較經典的內容。

用戶-權限 模型

對於權限管理,最簡單的想法應該是為每一個用戶,分配不同的權限。

如何從零開始設計權限管理系統

輸入圖片說明

但是這種設計存在一個比較大的問題就是不夠靈活,當用戶較多時,比較難以維護。

RBAC0 模型

於是,很多人想到了在用戶和權限中間增加一個角色。

也是迄今為止迄最為普及的權限設計模型,基於角色的訪問控制(Role-Based Access Control)。

如何從零開始設計權限管理系統

輸入圖片說明

這裡主要有三個概念:用戶,角色,權限。

用戶其實就是我們需要控制的主體。

角色就是一座橋樑,也是這個模型中最巧妙的地方。就像我們每一個人一樣,我們是父母的孩子,孩子的父母,公司的員工,我們在不同的時刻飾演着不同的角色。不同的角色,對應的不同的權限。

權限是一個比較寬泛的概念,對於控台而言,可能是菜單權限,資源權限,也包括對於數據的增刪改查的操作權限。

在數據庫表設計的時候,其實就是三張實體表:

user 用戶表

role 角色表

privilege 權限表

然後兩張映射表:

user_role 用戶-角色關係表

role_privilege 角色-權限關係表

以上是RBAC的核心設計及模型分析,此模型也叫做RBAC0,而基於核心概念之上,RBAC還提供了擴展模式。

如何從零開始設計權限管理系統

輸入圖片說明

包括RBAC1,RBAC2,RBAC3模型。

下面介紹這三種類型

RBAC1 模型

這裡主要是引入了角色權限繼承(Hierarchical Role)的概念。

如何從零開始設計權限管理系統

輸入圖片說明

這種設計可以給角色分組和分層,一定程度簡化了權限管理工作。

RBAC2模型

基於核心模型的基礎上,進行了角色的約束控制,RBAC2模型中添加了責任分離關係,其規定了權限被賦予角色時,或角色被賦予用戶時,以及當用戶在某一時刻激活一個角色時所應遵循的強制性規則。

不過實際工作中,這種模型用的也不多。此處不做展開。

RBAC3模型

功能最全的模型,也是各大公司最常用的一種模型。

我們平時見到的權限管理,一般是和公司的組織架構是一一對應的。

這就是,模型來源於生活。

用戶組

最常見的一個概念就是用戶組。

比如我們常用的 gitlab,加入一個項目組,然後就可以對相關的代碼倉庫進行操作。

就像我們在真正的工作中加入了一個項目組,是相同的道理。這裡的用戶一般是平級的。

公司中一般組織都是有上下級關係的,一般公司的組織架構圖如下:

如何從零開始設計權限管理系統

輸入圖片說明

這種架構的好處就是便於權限的變更調整,也便於權限的控制。

這種關係我們在設計表的時候,就可以加一個 user_group 表,將某些用戶放在同一個組中。

職位

職位其實對應的是角色的概念,不同的職位權限也是不同的,即使在同一個項目中。

就像項目負責人和普通開發之間的區別。

如何從零開始設計權限管理系統

輸入圖片說明

權限模型

最終的模型大概是下面的這個樣子:

如何從零開始設計權限管理系統

輸入圖片說明

授權流程

給用戶賦予角色,這個是如何實現的呢?

手動授權

最常見的是手動設計。

項目初始化的時候,有一些基本的角色和管理員等信息。

然後管理員進行相關的權限配置。

審批授權

用戶可以申請對應的角色,然後通過審批流程,賦予其對應的角色。

比如我們有時候可以申請訪問某一個資源,實際上背後也是類似的道理。

其實到這裡,都只是一些基礎知識。

如果想設計好菜單,還有很多東西需要考慮,比如菜單的設計,頁面的設計,流程怎麼設計更加合理?可以自定義角色嗎?用戶可以自定義菜單嗎?

等等。不過這些暫時我們不做深入討論,我們本篇的重點是自己實現一個簡單版本的權限控制框架。

傳統 RBAC 的不足

當然上面的模型還是存在不足之處的。

比如我讀的一篇 paper 中所言:

儘管RBAC已經得到廣泛應用,但傳統的RBAC模型仍存在不足之處,主要有以下兩個方面:(1)訪問控制不能滿足實際應用的需要,Web應用系統需要更加細粒度的訪問控制。 (2)該模型僅僅定義了訪問控制的內部機制,並沒有提出簡單友好的訪問控制實現方式,而對於Web系統的用戶而言,友好直觀的用戶接口是系統必不可少的組成部分。

解決方案是設計了一個操作碼,然後通過字符串標識比如: 10000 可以代表可訪問菜單,沒有增刪改查權限。

當然這些都需要我們結合自己的業務去設計。

我們假定設計產品設計完成,數據庫也設計完成的情況下,如何在編碼時靈活的實現權限控制呢?

設計思路

最基本的思路就是類似於 shiro 的方式。

可以指定角色,或者權限來訪問固定的菜單。

設計目標

(1)當然我們希望可以更加靈活,可以具體到固定的一個方法,而不是拘泥於固定的某一個 Controller 請求。

(2)編寫方便,後期可以使用註解指定

(3)由簡到繁,初期只實現基於權限編碼的控制,暫時不考慮基於角色的控制。因為每一個登錄的用戶,都可以獲取到對應的角色和權限編碼。

整體流程

  1. 用戶頁面請求
  2. 獲取用戶登錄信息。分佈式系統可以基於 redis session 或者 JWT 等,獲取當前用戶的所有權限編碼
  3. 校驗用戶是否擁有請求的權限
  4. 返回對應的頁面結果

第一步-接口設計

核心接口

public interface IPrivilege {

    /**
     * 是否擁有權限
     *
     * {@code true} 擁有
     * {@code false} 不擁有
     *
     * @param context 上下文
     * @return 是否
     * @since 0.0.1
     */
    boolean hasPrivilege(final IPrivilegeContext context);

}

這裡就是一切的核心,我們關心的只是是否有權限,結果就是一個 boolean 值。

那麼 context 的內容是什麼呢?

public interface IPrivilegeContext {

    /**
     * 擁有的權限上下文
     * @return 擁有
     * @since 0.0.1
     */
    IPrivilegeOwn own();

    /**
     * 需要的權限上下文
     * @return 擁有
     * @since 0.0.1
     */
    IPrivilegeAcquire acquire();

}

這裡實際上有兩個接口,一個是擁有的權限,另一個是需要的權限。

權限信息

兩個權限的內容如下:

  • 擁有的權限
public interface IPrivilegeOwn {

    /**
     * 擁有的權限編碼列表
     * @return 編碼列表
     * @since 0.0.1
     */
    List<IPrivilegeInfo> ownPrivilege();

}
  • 需要的權限
public interface IPrivilegeAcquire {

    /**
     * 需要的權限編碼列表
     * @return 編碼列表
     * @since 0.0.1
     */
    List<IPrivilegeInfo> acquirePrivilege();

}

第二步-編程式實現

maven 引入

<dependency>
    <group>com.github.houbb</group>
    <artifact>privilege-core</artifact>
    <version>${最新版本}</version>
</dependency>

自定義擁有的權限

實現 IPrivilegeOwn 接口即可。

可以查詢數據庫,文件等。

public class PrivilegeOwnOne implements IPrivilegeOwn {
    @Override
    public List<IPrivilegeInfo> ownPrivilege() {
        IPrivilegeInfo info = PrivilegeInfo.newInstance().code("001");
        return Collections.singletonList(info);
    }
}

自定義需要的權限

public class PrivilegeAcquireTwo implements IPrivilegeAcquire {

    @Override
    public List<IPrivilegeInfo> acquirePrivilege() {
        IPrivilegeInfo one = PrivilegeInfo.newInstance().code("001");
        IPrivilegeInfo two = PrivilegeInfo.newInstance().code("002");
        return Arrays.asList(one, two);
    }

}

測試1

IPrivilegeOwn own = new PrivilegeOwnOne();
IPrivilegeAcquire acquire = new PrivilegeAcquireTwo();
boolean hasPrivilege = PrivilegeBs.newInstance()
        .own(own)
        .acquire(acquire)
        .hasPrivilege();

Assert.assertFalse(hasPrivilege);

這裡我們得到的結果是沒有權限,因為默認的策略是指定的編碼全部擁有,才算通過。

內置策略

內置的策略,可以通過 Privileges 直接獲取。

策略 說明 all() 需要的權限編碼,全部都擁有,才認為擁有權限。(默認策略) any() 需要的權限編碼,擁有任何一個,則認為擁有權限。 allow() 白名單,直接返回擁有權限。 deny() 黑名單,直接返回不擁有權限。

測試2

我們指定擁有一個就可以通過

IPrivilegeOwn own = new PrivilegeOwnOne();
IPrivilegeAcquire acquire = new PrivilegeAcquireTwo();
IPrivilege privilege = Privileges.any();

boolean hasPrivilege = PrivilegeBs.newInstance()
        .own(own)
        .acquire(acquire)
        .privilege(privilege)
        .hasPrivilege();

Assert.assertTrue(hasPrivilege);

第三步-註解式編程

註解的必要性

當然上面已經實現了一個最基本的實現,但是實際使用肯定不能讓開發者手動指定。

那該多麻煩。

敘事我們設計可以基於註解的實現。

註解定義

@Inherited
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrivilegeAcquire {

    /**
     * 權限編碼
     * @return 編碼
     * @since 0.0.4
     */
    String[] code() default {};

}

這個註解可以放在方法上,指定訪問時需要的編碼。

maven 引入

我們一般開發,都是在 spring-mvc 項目中使用。

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>privilege-mvc</artifactId>
    <version>${最新版本}</version>
</dependency>

自定義攔截器

我們定義一個屬於自己的攔截器,用來處理登錄用戶的權限。

@Component
public class MyPrivilegeInterceptor extends AbstractPrivilegeInterceptor {

    @Override
    protected void fillSubject(ISubject subject, HttpServletRequest request) {
        String id = request.getParameter("id");
        if("admin".equals(id)) {
            subject.privileges("1001");
        }
    }

}

我們只為 admin 用戶設置權限編碼為 1001,其他用戶不做處理。

註冊攔截器

將我們的攔截器註冊一下,當訪問 /hello 這個地址的時候,會生效。當然也可以根據實際情況調整。

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private MyPrivilegeInterceptor myPrivilegeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myPrivilegeInterceptor)
            .addPathPatterns("/hello");
    }

}

業務代碼

Controller

我們通過註解 @PrivilegeAcquire 指定訪問當前方法時,需要權限編碼 1001

@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    @PrivilegeAcquire(code = "1001")
    public String hello() {
        return "hello";
    }

}

Application

啟動類定義如下:

我們通過註解 @EnablePrivilege 啟動權限校驗。

@SpringBootApplication
@EnablePrivilege
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

測試

頁面訪問 http://localhost:8080/hello 報錯權限不足;

There was an unexpected error (type=Internal Server Error, status=500).
Has no privilege! Acquire: <[1001]>, Own: <[]>

頁面訪問 http://localhost:8080/hello?id=admin 正常訪問。

小結

到這裡一個最簡單的權限設計就完成了。

實際上設計的時候,為了適應各種場景,項目是非常多個模塊的:

(1)api 核心的接口和註解定義

(2)core 編程式的基本實現,可以脫離 spring 存在

(3)proxy 脫離 spring 可以直接使用的代理

(4)spring 結合 spring 的 AOP 模式

(5)mvc 結合 spring-mvc,也是最常用的一種模式

後續將考慮繼續優化,添加對於 springboot 的支持。

也考慮學習一下 shiro 的精髓,引入更多的特性。

開源地址

https://github.com/houbb/privilege

歡迎 fork/star~~

願景

框架可以支持更多的特性,比如基於角色,基於用戶。

開箱即用的設計,比如最基本的 sql + mybatis 的通用權限管理框架,類似於 quartz。

一個完整的項目設計,支持前後端頁面。

單獨的服務打磨,可以對外提供鑒權服務,結合 auth 等,進一步優化權限管理。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/322865.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2025-01-12 12:43
下一篇 2025-01-12 12:43

相關推薦

發表回復

登錄後才能評論