简介:这份《访问控制理论与策略》压缩包面向网络安全学习者、Java Web开发者以及需要搭建权限系统的工程人员,围绕主体、客体与操作三要素和RBAC0模型展开,可用于课程设计、项目实践与权限体系梳理,帮助解决权限边界划分与访问决策落地问题。包内共671个文件,以115个class、68个java源码、40个jsp页面、46个xml配置、63个jar依赖以及50个css、31个js等前端资源为主,较完整地呈现从后端权限判断到页面交互的实现脉络,压缩包约59.74MB。内容重点落在用户、角色、权限的关系设计,以及角色管理、用户管理、权限维护和访问控制决策等源码模块,读者可据此理解固定角色、职责分离、委派管理和非侵入式检查等特性,并对照网络服务、企业信息系统与云计算平台等场景梳理权限分配流程。已有190人学习,适合需要把RBAC0从概念落到代码结构中的读者参考。
1. 权限判断不该写在业务里:为什么访问控制要先拆出决策层
很多人第一次做访问控制,是在业务代码里塞一个if (user.role === 'admin')。这种写法在演示环境几乎不会出错,可一旦进入网络安全审计流程,往往第一批就被打回。原因不在于判断写得对不对,而在于判断的依据从哪来、在哪一层判、判完之后能不能被绕过。这三点决定了一个看起来只有四张表的 RBAC0 模型,为什么能支撑起大量真实生产系统:它把“谁能做什么”从散落的分支语句里抽出来,变成了可以被统一求值、统一审计、统一变更的数据。
值得较真的是这层抽象背后的代价。访问控制不是加密,它不提供任何数学上的不可破解性,它提供的是一套可被验证的决策流程。理解这套流程为什么长成这样,比背下 DAC、MAC、RBAC 的定义有用得多。
2. 访问控制矩阵:为什么所有模型最终都退回同一张稀疏表
把任意一个访问控制系统的判定过程拆到最底层,都会露出同一个结构:一个主体、一个客体、一个操作,然后回答允许还是拒绝。理论上的标准形态是访问控制矩阵,行是主体,列是客体,格子里是操作集合。几乎所有教材都从这里开始,但几乎没有人真的把它存成一张二维表,原因很现实——矩阵太大了。
一个中等规模系统,用户几十万,客体(文件、订单、字段、接口)上千万,矩阵的元素数量是天文数字,而其中非空的比例通常不到万分之一。存储一张几乎全空的表,是纯粹的浪费。于是所有工程实现做的事情本质上是同一件:压缩稀疏矩阵,只不过压缩的方向不同。
2.1 主体、客体、操作:三要素如何覆盖一切鉴权场景
主体不一定是人。进程、服务账号、一次定时任务、某个下游系统回调,都可以是主体。客体也不一定是文件,一行订单记录、一个 API 路径、一列手机号、一个按钮的点击行为,都能当客体。操作则是动词的抽象:读、写、执行、审批、导出、脱敏查看。
把这三者抽象出来,好处是判定逻辑可以完全与业务解耦。业务只需要在关键位置抛出“谁要对什么做什么”,由决策层返回结果。这也是为什么成熟的权限系统会拆成 PEP(策略执行点)和 PDP(策略决策点):前者贴近业务,负责拦截和收集上下文;后者独立部署,负责求值和返回。
有一个容易被忽略的细节:客体的粒度决定了整个系统的复杂度。如果客体是“订单”,权限模型可能十张表就够;如果客体是“订单里的某一列”,那 RBAC 的粗粒度就不够用了,必须引入属性或标签。选型上的很多争论,其实是在争论客体粒度,而不是模型本身。
2.2 矩阵为什么必须被压缩:从二维表到三层关系
压缩方式主要有三种,对应三种截然不同的查询性能特征。
第一种是按列存,也就是 ACL(访问控制列表):每个客体挂一个“谁能操作我”的列表。文件系统用的就是这个思路,chmod的权限位本质上是一张极简 ACL。它的优势是判定快——拿到客体就能立刻回答“这个主体能不能访问”,因为列表就在客体旁边。
第二种是按行存,也就是能力表(capability):每个主体持有一组“我能操作谁”的凭证。令牌、临时凭证、签名 URL 都属于这一类。它的优势是分发方便,主体拿着凭证就能访问,不需要每次都回源查询;劣势是撤销困难,凭证发出去之后想收回,要么等它过期,要么维护一张吊销名单。
第三种是按关系存,也就是把三元组拆成关系表。这是业务系统里最常见的做法,因为权限往往需要按组织、按项目、按角色批量授予和批量撤销,而关系模型天然适合做集合运算。代价是每次判定都要做多次连接,所以必须在中间加缓存。
提示:ACL 和能力表不是二选一。真实的系统常常两层都用——外层用能力表做快速准入,内层用 ACL 做最终校验,任何一层判定不一致都视为拒绝。
把三种压缩方式和查询模式对照一下,选型的思路就清楚了:
| 存储形态 | 判定入口 | 典型查询 | 撤销成本 | 适用场景 |
|---|---|---|---|---|
| ACL(按客体) | 客体 | 谁能访问这个资源 | 低 | 文件、文档、资源详情 |
| 能力表(按主体) | 主体 | 这个主体能访问哪些资源 | 高 | 临时授权、跨服务调用 |
| 关系表(三元组) | 策略库 | 两者皆可,靠索引 | 中 | 业务系统、组织级授权 |
2.3 引用监视器:检查点必须卡在不可绕过的位置
矩阵解决了“存什么”,引用监视器解决了“在哪判”。这个概念来自安全内核的理论:系统中必须存在一个中介,所有访问请求都必须无条件经过它,且它本身不能被绕过、不能被篡改、必须足够小以便被验证。
现实里最常见的翻车,就是把检查点放在了可以被绕开的位置。比如只在网关层校验,内部服务之间直接调用就跳过了;比如只在前端隐藏按钮,接口照样能被直接请求。引用监视器的要求是:绕过它就不能完成访问,而不是“绕过它会不太方便”。
一个最小骨架大致长这样:
def reference_monitor(subject, obj, action, policy_store, audit): # 1. 身份必须来自不可伪造的凭证,不能来自请求体里的 user_id if not subject.is_authenticated(): audit.deny(subject, obj, action, reason="unauthenticated") return False # 2. 策略是数据,从策略源求值,不散落在业务分支里 decision = policy_store.evaluate(subject.id, obj.id, action) # 3. 默认拒绝:没有匹配到任何显式 allow,就是拒绝 allowed = decision.effect == "allow" # 4. 先落审计再返回,保证拒绝也有迹可循 audit.record(subject, obj, action, allowed, decision.matched_rule) return allowed这段代码里,subject的身份校验必须独立于业务参数,policy_store是可以热更新、可以版本化的策略源,audit的写入顺序在返回之前——否则一次放行之后的崩溃会让审计缺失。decision.matched_rule这个字段很关键,它让事后追查能直接定位到是哪条规则放行的,而不是只知道“被允许了”。
真正的难点在于让整个系统只有这一个入口。多语言、多服务、多团队协作时,这件事靠的不是代码规范,而是架构约束:把资源访问收敛到少数几个 SDK 或边车,让绕过检查的成本高于走检查的成本。
3. DAC 与 MAC:自主授权为什么会自己长权限,强制标签又兜住了什么
访问控制模型的两大源头是自主访问控制(DAC)和强制访问控制(MAC)。它们的差别不在于严格程度,而在于“谁有权改规则”。DAC 把授权能力交给资源所有者,灵活、贴近业务,代价是权限会沿着人际关系扩散;MAC 把规则交给系统,主体和客体都带标签,判定由标签比较得出,所有者也无权改变。
这个差别在理论上很清楚,落到工程上却经常被混用。很多系统自称 RBAC,实际上底层还是 DAC——因为角色可以被管理员随意分配,分配的依据是人的判断而不是系统规则。这不算错,但要知道自己失去了什么:一旦授权链条足够长,追溯“这个权限最初是谁给的”就会变成一件非常痛苦的事。
3.1 DAC 的授权传递:一个所有者的疏忽如何变成全网可达
DAC 的核心操作是“授予”。所有者可以把访问权转授给别人,别人还可以继续转授。这在协作场景里非常自然,但它构建出的是一张有向图,而图是有连通性的。
设想一个内容协作系统:某个项目负责人把自己的一份文档共享给了外部合作方,合作方又把它共享给了自己的同事。这个链条本身没问题,问题在于链条上的每个节点都可能成为新的扩散源。一旦某个中间账号被盗用,攻击面不是“这一份文档”,而是这个账号能触达的所有资源,以及它还能继续转授出去的能力。
常见缓解手段是限制转授深度,或者引入“不可再授权”标志位。但更根本的做法是记录授权来源,让每一次授权都带上祖先链。这样在撤销时,可以沿着链一次性收回派生权限,而不是指望人工逐个排查。
3.2 BLP 的两条铁律:no read up 与 no write down 分别在防什么
强制访问控制的经典模型是 BLP,它用两个性质保证机密性:简单安全性(主体不能读取比自己级别更高的客体)和星号性质(主体不能向比自己级别更低的客体写入)。很多人只记住了“上读下写都不行”,却没想清楚为什么。
不允许向上读,是为了防止低权限者直接获取高密级信息。不允许向下写,是为了防止高权限者把高密级信息“降级”写进低密级容器里——如果不拦这一步,前面那条规则就形同虚设,因为信息可以从上层流下来。
LEVELS = {"public": 0, "internal": 1, "secret": 2, "top": 3} def blp_read(subject, obj): # 简单安全性:许可级别低于客体级别时拒绝 if LEVELS[subject.clearance] < LEVELS[obj.classification]: return False # 类别集合必须覆盖客体类别,防止同级不同域的横向读取 return set(obj.categories).issubset(set(subject.categories)) def blp_write(subject, obj): # 星号性质:许可级别高于客体级别时拒绝,防止高密级信息向下流动 if LEVELS[subject.clearance] > LEVELS[obj.classification]: return False return set(obj.categories).issubset(set(subject.categories))clearance是主体的许可级别,classification是客体的密级,categories是领域划分(比如财务、人事)。只比较级别是不够的,还必须比较类别集合,否则两个同级别但不同领域的主体就能互相读取。
这套规则在实际系统里会撞上一个尴尬:如果主体的级别高于它需要写入的客体的级别,它写不进去。解决办法通常是引入“可信降级过程”——一个被显式授权、行为被审计的组件,允许它在受控条件下完成降级写入。这个组件的存在本身就是系统最脆弱的一环,所以它必须足够小、足够显眼。
3.3 Biba 与 BLP 的对偶:完整性和机密性为什么方向相反
BLP 保机密性,Biba 保完整性,两者的规则几乎是镜像的:Biba 要求主体不能读取完整性级别比自己低的客体(不读脏数据),也不能向完整性级别比自己高的客体写入(不许污染干净数据)。
这个对偶关系初看很别扭,想通之后会觉得非常合理。机密性的威胁是信息外流,所以要阻止高到低的流动;完整性的威胁是数据被污染,所以要阻止低到高的流动。方向相反,是因为保护的目标相反。
工程上很少有系统纯用其中一种。更常见的做法是分区:涉及敏感数据的模块按 BLP 思路打标签,涉及核心配置和账务的模块按 Biba 思路打标签,中间靠受控的转换层衔接。这也解释了为什么纯 MAC 系统落地的成本很高——它要求先对整个数据资产做完整的标签化,而这件事在很多组织里比写权限代码难得多。
3.4 隐蔽通道:强制模型也堵不住的那条路
强制访问控制能管住显式的读写,但管不住信息以其他形式流动。典型的隐蔽通道有两类:存储通道和时间通道。
存储通道指的是利用系统资源的可见状态传递信息。比如一个高密级进程通过“某个文件是否存在”来表达一个比特,低密级进程只要观察文件系统就能读出来,全程没有发生任何一次被策略允许的读取。时间通道则更隐蔽:高密级进程通过控制自己的 CPU 占用或响应延迟,把信息编码进时间维度。
要压制这类通道,通常的做法是降低共享资源的可观测精度,比如对资源使用情况做模糊化、对响应时间加随机抖动、限制低密级主体能看到的系统统计信息。这些手段会带来性能损失,而且没法彻底根除,只能把通道容量压到可接受的范围。这也是为什么高安全等级的系统对共享资源的管控会严到影响体验——它不是过度设计,而是承认了通道无法被消灭。
4. RBAC:角色这层间接引用,到底替谁挡掉了复杂度
RBAC 的贡献不在于“把权限给角色”,而在于把用户和权限之间的多对多关系压缩成了两个一对多关系。用户数乘以权限数是一个巨大的笛卡尔积,而用户到角色、角色到权限这两条边的数量要小得多。授权管理的成本因此从“按人配”变成了“按岗位配”。
RBAC0 是最小完备集,它只包含用户、角色、权限、会话四类元素,以及用户-角色、角色-权限两组分配关系。很多系统自称实现了 RBAC,其实只做了用户-角色这一半,权限仍然硬编码在代码里,那就退化成了一种粗糙的 DAC。
4.1 RBAC0 的最小完备集:四张表与一次会话
最小实现需要六张表:三张实体表加三张关系表,其中会话是两张。会话这个概念经常被忽略,但它才是 RBAC0 区别于“角色字段”的关键。
CREATE TABLE app_user ( id BIGINT PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE role ( id BIGINT PRIMARY KEY, name TEXT NOT NULL UNIQUE ); -- 权限用资源 + 动作描述,避免一个权限对应一个硬编码常量 CREATE TABLE permission ( id BIGINT PRIMARY KEY, resource TEXT NOT NULL, action TEXT NOT NULL, UNIQUE (resource, action) ); CREATE TABLE user_role ( user_id BIGINT REFERENCES app_user(id), role_id BIGINT REFERENCES role(id), PRIMARY KEY (user_id, role_id) ); CREATE TABLE role_permission ( role_id BIGINT REFERENCES role(id), permission_id BIGINT REFERENCES permission(id), PRIMARY KEY (role_id, permission_id) ); -- 会话:一次登录期间激活的角色子集,属于运行时概念,不是持久授权 CREATE TABLE session ( id TEXT PRIMARY KEY, user_id BIGINT REFERENCES app_user(id), started_at TIMESTAMP NOT NULL ); CREATE TABLE session_role ( session_id TEXT REFERENCES session(id), role_id BIGINT REFERENCES role(id), PRIMARY KEY (session_id, role_id) );permission表用resource + action而不是枚举常量,是为了让权限可以随资源配置自动生成,而不是每加一个接口就改一次代码。session_role独立于user_role,是因为用户拥有的角色和当前激活的角色不是一回事——一个用户可能同时拥有审批和复核两个角色,但在处理同一笔业务时只能激活其中一个,这就是后面要讲的动态职责分离的基础。
这段建表里没有外键级联删除,是有意的。权限数据的删除应该走显式流程并留痕,级联删除会让一次误操作变成一次无法追溯的权限消失。
4.2 角色继承的偏序:RBAC1 为什么不是简单的树
RBAC1 在 RBAC0 上加了角色层次。高层角色继承低层角色的权限,这样“部门管理员”可以继承“普通成员”的全部权限,再附加自己的管理权限。
关键点在于,这个层次结构是偏序,不是树。一个角色可以同时继承两个父角色,比如“区域财务主管”既继承“财务专员”又继承“区域管理员”。一旦允许多继承,结构就从树变成了有向无环图,而图就必然要处理环的问题。
环一旦出现,递归求值会直接爆栈或者超时。防环的手段有两层:写入时校验,把“A 继承 B”插入之前先检查 B 是否已经可达 A;读取时兜底,用UNION而不是UNION ALL做递归,让重复节点被去重,从而在环存在时也能收敛。
角色爆炸是另一个高频问题。业务方常常要求“这个角色和那个角色只差一个权限”,于是每来一个新需求就新建一个角色,最后角色数量比用户还多,维护成本彻底失控。缓解方式是给角色加上领域归属,把角色限制在某个业务域内生效,跨域的组合通过会话激活来完成,而不是造一个全局巨无霸角色。
4.3 SSD 与 DSD:职责分离约束怎么在关系模型里落地
职责分离的目的是防止单点拥有过大的权力。经典例子是付款和审批不能由同一个人完成,否则内控就失效了。
静态职责分离(SSD)约束的是用户-角色分配:一个用户不能被同时授予互斥的两个角色,这条规则在分配时检查,一次校验就够。动态职责分离(DSD)约束的是会话:用户可以同时拥有两个互斥角色,但在同一个会话里只能激活其中一个,需要切换时重新建立会话。
DSD 的落地点就在session_role上。每次激活角色前,都要检查当前会话已激活的角色集合与待激活角色是否构成互斥对。这个检查必须和激活动作在同一个事务里完成,否则并发激活两个互斥角色就能绕过约束。
约束本身的表达也需要落表,而不是写死在代码里:
-- 互斥角色对:两个角色不能同时出现在同一会话中 CREATE TABLE role_exclusion ( role_a BIGINT REFERENCES role(id), role_b BIGINT REFERENCES role(id), CHECK (role_a < role_b), PRIMARY KEY (role_a, role_b) );CHECK (role_a < role_b)保证同一对角色只存一行,避免正反两行导致的校验遗漏——这是很容易漏掉的一个细节,判断互斥时如果只查一个方向,另一条记录就等于不存在。
4.4 决策查询怎么写:一条 SQL 讲清用户到权限的路径
把角色继承、会话激活、权限展开合到一次查询里,是理解 RBAC 求值过程最直接的方式。下面是带角色继承的版本:
WITH RECURSIVE effective_role AS ( -- 起点:本次会话激活的角色 SELECT r.id FROM role r JOIN session_role sr ON sr.role_id = r.id WHERE sr.session_id = :session_id UNION -- 用 UNION 而非 UNION ALL,去重的同时防止继承成环导致无限递归 -- 递归:沿继承关系向上展开父角色 SELECT rh.parent_id FROM role_hierarchy rh JOIN effective_role er ON rh.child_id = er.id ) SELECT DISTINCT p.resource, p.action FROM effective_role er JOIN role_permission rp ON rp.role_id = er.id JOIN permission p ON p.id = rp.permission_id;参数只有session_id一个,因为用户身份已经从会话里推导出来,不再从请求参数读取——这一点很关键,多传一个user_id参数就等于多开了一个越权入口。
role_hierarchy表需要两个字段parent_id和child_id,表示子角色继承父角色的全部权限。递归的起点是会话激活的角色,而不是用户拥有的全部角色,这个区别决定了 DSD 是否真的生效。
这条查询的代价是每次判定都要递归,所以在真实系统里必须缓存。缓存的键要包含角色版本号,否则角色变更后旧决策会在缓存里继续生效,形成一个看不见的越权窗口。
5. 权限系统排查:五类越权问题为什么在测试环境永远看不到
权限相关的缺陷有个共同特征:它们在功能测试里几乎不出现,因为测试用例总是用“正确的用户”访问“正确的资源”。真正的问题往往藏在状态变更、并发、缓存和边界条件里。下面这几类是我见过反复出现、且每次都要花不少时间定位的。
5.1 越权只在缓存过期后出现
现象:修改了用户的角色,界面上权限变了,但用户仍然能访问刚被收回的资源;等一段时间之后,问题自己消失了。
原因:决策结果被缓存,但缓存的键只包含用户和资源,没有包含角色版本。角色变更后键不变,旧决策继续命中,直到 TTL 到期。这个窗口的长度等于缓存过期时间,而它通常没人记得。
解决:把角色版本号纳入缓存键,用户角色或角色权限发生变更时自增版本。缓存键的构造必须显式列出所有影响判定的维度。
def cache_key(tenant, user_id, role_version, resource, action): # role_version 在角色分配或角色权限变更时自增,缺失任何一维都会导致决策过期 return f"acl:v1:{tenant}:{user_id}:{role_version}:{resource}:{action}"tenant不能省,多租户场景下相同 user_id 在不同租户里可能指向不同主体;v1是键格式版本,用于结构变更时整体作废旧缓存。
5.2 角色继承成环导致决策递归爆栈
现象:给某个角色配置了继承关系后,接口开始超时,日志里出现递归深度异常或数据库连接被占满。
原因:角色层次被写成了有环图,比如 A 继承 B、B 继承 C、C 又继承 A。写入时没有做可达性校验,读取时的递归查询又没有去重,于是无限展开。
解决:读取侧必须用UNION去重兜底,写入侧必须加可达性检查——在插入继承边之前,先查一遍父角色是否已经能到达子角色,能到达就拒绝。读取侧是兜底,写入侧才是根治,只做读取侧会留下一个隐形的性能陷阱。
5.3 前端隐藏按钮被当成权限控制
现象:审计时被告知存在越权,但复现时发现“正常用户界面上根本看不到那个按钮”。
原因:权限判断只发生在渲染层,接口本身没有校验,或者只校验了登录态没校验权限。攻击者直接请求接口就能绕过全部限制。
解决:前端隐藏只处理体验,不承担安全职责。所有涉及资源变更的接口必须在服务端做一次完整的决策求值,且这次求值不能依赖前端传来的角色字段。检查点要放在引用监视器的位置上,也就是所有请求的必经路径,而不是散落在各控制器里。
5.4 会话里激活的角色未随登出清理
现象:用户切换账号或者重新登录后,仍然保留着上一个账号的部分权限,刷新页面后恢复正常。
原因:会话状态存在服务端缓存里,登出时只清了客户端的 token,没有清服务端的会话记录;或者新旧会话 ID 复用了同一个键,导致角色集合被合并。
解决:登出必须显式销毁服务端会话及其激活角色,会话 ID 必须在每次登录时重新生成。判断权限时以服务端会话为准,不信任任何客户端携带的角色声明。
5.5 策略冲突时“默认拒绝”被覆盖
现象:一条显式拒绝规则明明已经配置,实际访问却被放行了。
原因:多条策略同时命中同一个请求时,合并顺序写错了。常见错误是把默认规则放在最后求值,或者把“拒绝”当成异常处理,结果异常被上层捕获后走了放行分支。
解决:明确合并语义并写死。推荐拒绝优先,且默认拒绝作为兜底而非可覆盖的规则。评估顺序固定为:显式拒绝 → 显式允许 → 默认拒绝。只要有一条拒绝命中,就直接短路,不再继续求值。
def merge_decisions(decisions): # 拒绝优先:任意一条 deny 命中即终止,不给后续规则覆盖的机会 if any(d.effect == "deny" for d in decisions): return "deny" if any(d.effect == "allow" for d in decisions): return "allow" # 没有任何规则命中时的兜底必须是拒绝 return "deny"decisions是本次请求命中的全部策略求值结果,顺序不影响最终结论,这样就不会因为策略加载顺序不同而产生不一致的行为。
6. 模型选型的取舍:为什么大多数系统最终停在混合带
纯模型在实践中很少见,原因不是模型不好,而是每种模型都假设了某种前提。DAC 假设资源所有者有能力做出正确判断;MAC 假设整个组织已经完成了完整的标签化和等级划分;RBAC 假设岗位职责足够稳定,可以用角色概括;ABAC 假设属性来源可靠、策略可评估。这些假设在真实组织里往往只成立一部分。
所以实际系统的形态通常是分层混合:用 RBAC 管粗粒度的功能权限,因为岗位和职能相对稳定,角色不会天天变;用 ABAC 管数据级权限,因为“只能看自己部门的订单”这类规则无法用有限角色穷举;用 MAC 的标签思路处理少数高敏感数据域,把它隔离出来,不强求全系统统一。
| 模型 | 判定依据 | 灵活度 | 管理成本 | 常见误用 |
|---|---|---|---|---|
| DAC | 所有者授权 | 高 | 随授权链增长失控 | 转授无深度限制 |
| MAC | 主体与客体标签 | 低 | 前期标签化极重 | 只比级别不比类别 |
| RBAC | 角色 | 中 | 角色易膨胀 | 只做用户-角色,权限硬编码 |
| ABAC | 属性与策略 | 高 | 属性质量难保证 | 属性来源不可信 |
选型时真正该问的问题不是“哪个模型更先进”,而是“权限变更的频率和粒度是什么”。变更频率低、粒度粗,RBAC 足够;粒度细到行和列、且规则随业务数据变化,就必须引入属性;监管要求能证明信息不会向下流动,才需要考虑标签体系。反过来,如果只是内部工具,上一整套 ABAC 策略引擎,光是属性同步的链路就够维护很久。
验证一个权限设计是否站得住,有个很朴素的测试方法:随机挑一个用户,列出他能访问的全部资源,然后逐条问“这条权限是谁在什么时候因为什么给的”。如果有任何一条答不上来,说明授权链缺少可追溯性,出问题时就没有后悔药可吃。这个检查不需要工具,但极其有效。
我自己的习惯是,每当要新增一个角色或一条策略,先强制走一遍这个追问:它能不能被现有角色组合表达,它和其他规则冲突时谁优先,它的变更会不会影响正在进行的会话。从那以后我每次调整权限模型,都会先把“撤销路径”和“冲突顺序”写在设计文档最前面,而不是等出事之后再补。希望帮到你。
这套资料把访问控制矩阵、DAC 与 MAC、BLP 与 Biba、RBAC0 到 RBAC3 的模型定义和策略样例按主题整理在一起,想对着定义逐条核对实现细节的,可以直接取用。
本文还有配套的精品资源,点击获取