Linux 数字权限设计方法
Linux 权限的数字表示法
在 Linux 里,每个文件和目录都有权限设置,控制谁能对它做什么。权限分三档:读、写、执行,用数字来编码之后,配置起来非常直观。
三种基本权限对应的数字
- 读(read)— 数字
4,可以查看文件内容,对目录来说就是能ls列出文件 - 写(write)— 数字
2,可以修改文件内容,对目录来说就是能在里面创建、删除文件 - 执行(execute)— 数字
1,可以运行文件作为程序,对目录来说就是能cd进入
0 表示没有任何权限。
为什么偏偏选 4、2、1 这三个数?因为它们各自是 2 的幂次(2²、2¹、2⁰),对应二进制中的三个独立位。每个权限独占一个 bit,加起来的结果不会产生歧义。不存在”5到底是4+1还是3+2”的困惑,因为 3 本身就不是一个基础权限位,它已经是 2+1 的组合。这套设计的精髓就在于:任意组合的求和结果在 0-7 之间都是唯一的,不会出现两种不同权限组合得到同一个数字的情况。
用二进制来看就非常直观,每个 bit 对应一种权限,1 表示有,0 表示没有:
| 十进制 | 二进制 | 读(4) | 写(2) | 执行(1) | 含义 |
|---|---|---|---|---|---|
0 | 000 | 0 | 0 | 0 | 无权限 |
1 | 001 | 0 | 0 | 1 | 只执行 |
2 | 010 | 0 | 1 | 0 | 只写 |
3 | 011 | 0 | 1 | 1 | 写 + 执行 |
4 | 100 | 1 | 0 | 0 | 只读 |
5 | 101 | 1 | 0 | 1 | 读 + 执行 |
6 | 110 | 1 | 1 | 0 | 读 + 写 |
7 | 111 | 1 | 1 | 1 | 读 + 写 + 执行 |
看 5 那行,二进制 101——读那列是 1,写那列是 0,执行那列是 1,一目了然,没有任何歧义的余地。
所以 chmod 755 翻译成二进制就是:
1
2
3
所有者: 7 = 111 (读 + 写 + 执行)
同 组: 5 = 101 (读 + 执行)
其 他: 5 = 101 (读 + 执行)
三类用户身份
Linux 里权限是按用户身份分开设置的,一共有三类:
- 所有者(owner) — 文件的拥有者,通常是创建它的人
- 同组用户(group) — 和所有者同属一个用户组的其他人
- 其他用户(other) — 既不是所有者也不在同一个组的其他人
所以一个完整的权限需要给三类身份各分配一个数字,写成三位数。比如 chmod 755 的意思是:
- 所有者:
7(读 + 写 + 执行) - 同组用户:
5(读 + 执行) - 其他用户:
5(读 + 执行)
常见的 chmod 用法
日常开发里用得最多的几个权限组合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 脚本文件,所有人都能执行,但只有所有者能改
chmod 755 deploy.sh
# 配置文件,只有所有者能读写,其他人完全无权限
chmod 600 .env
# 普通文档,所有者可读写,其他人只读
chmod 644 README.md
# 目录,所有人都能进入和查看,但只有所有者能增删文件
chmod 755 src/
# 递归修改目录下所有文件的权限
chmod -R 644 docs/
用 ls -l 查看权限
执行 ls -l 看到的 -rwxr-xr-x 其实就是数字权限的字母版:
1
2
3
4
5
6
-rwxr-xr-x 1 pe7er staff deploy.sh
│├─┤├─┤├─┤
│ │ │ └── other: r-x = 5
│ │ └───── group: r-x = 5
│ └──────── owner: rwx = 7
└────────── 文件类型(- 普通文件,d 目录,l 链接)
所以 -rwxr-xr-x 对应的数字就是 755。
几个容易踩的坑
不要图省事直接 chmod 777。777 意味着所有人都能随便改你的文件,在生产服务器上这样做等于开了个安全漏洞。绝大多数场景下,755 给目录、644 给文件就够了。
.env、私钥文件这类敏感配置,权限一定要设成 600 甚至 400,只让自己能读。很多工具(比如 SSH)检测到私钥权限太宽松会直接拒绝工作。
chmod -R 递归修改的时候要注意,目录和文件的合理权限不一样——目录通常需要 755(需要执行权限才能进入),文件通常 644 就够。可以用 find 分开处理:
1
2
3
# 目录设 755,文件设 644
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
速查表
| 权限值 | 所有者 | 同组 | 其他 | 适用场景 |
|---|---|---|---|---|
755 | rwx | r-x | r-x | 可执行脚本、目录 |
644 | rw- | r– | r– | 普通文件、配置文件 |
600 | rw- | — | — | 敏感文件(密钥、.env) |
700 | rwx | — | — | 私有脚本或目录 |
400 | r– | — | — | 只读敏感文件 |
位掩码权限设计在应用系统中的落地
Linux 用三个 bit 表示三种权限,这个思路搬到业务系统里一样好使,而且比一堆布尔字段或字符串数组要干净得多。
基本思路:每个权限占一个 bit
假设一个订单模块有这些操作权限:
| 权限 | 二进制位 | 十进制值 |
|---|---|---|
| 查看订单 | 0001 | 1 |
| 创建订单 | 0010 | 2 |
| 编辑订单 | 0100 | 4 |
| 删除订单 | 1000 | 8 |
和 Linux 的设计一模一样——每个权限独占一个 bit,值是 2 的幂次。
用位运算组合和判断权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义权限常量
const PERM = {
VIEW: 1 << 0, // 0001 = 1
CREATE: 1 << 1, // 0010 = 2
EDIT: 1 << 2, // 0100 = 4
DELETE: 1 << 3, // 1000 = 8
}
// 给角色分配权限:用 | (按位或) 组合多个权限
const adminPerm = PERM.VIEW | PERM.CREATE | PERM.EDIT | PERM.DELETE // 1111 = 15
const editorPerm = PERM.VIEW | PERM.CREATE | PERM.EDIT // 0111 = 7
const viewerPerm = PERM.VIEW // 0001 = 1
// 判断角色是否有某个权限:用 & (按位与)
function hasPermission(rolePerm: number, required: number): boolean {
return (rolePerm & required) === required
}
hasPermission(editorPerm, PERM.EDIT) // true (0111 & 0100 = 0100)
hasPermission(editorPerm, PERM.DELETE) // false (0111 & 1000 = 0000)
hasPermission(adminPerm, PERM.DELETE) // true (1111 & 1000 = 1000)
1 << n 是位运算的左移操作,把 1 往左移 n 位,效果就是 2 的 n 次方。写法比直接写 1, 2, 4, 8 直观,而且新增权限时只需要加一行 1 << 4,不用自己算下一个 2 的幂是多少。
多模块权限:用多段 bit 拼
实际业务里往往不止一个模块。订单有增删改查,商品也有增删改查。可以按模块分段,每 4 个 bit 一个模块:
1
2
3
4
5
商品模块 订单模块
---- ----
管理员: 1111 1111 = 0xFF
运营: 1110 0111 = 0xE7
客服: 0001 0001 = 0x11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ORDER_VIEW = 1 << 0
const ORDER_CREATE = 1 << 1
const ORDER_EDIT = 1 << 2
const ORDER_DELETE = 1 << 3
const PRODUCT_VIEW = 1 << 4
const PRODUCT_CREATE = 1 << 5
const PRODUCT_EDIT = 1 << 6
const PRODUCT_DELETE = 1 << 7
// 运营角色:订单可增改查(0111),商品可增改查但不可删(1110)
const opsPerm = ORDER_VIEW | ORDER_CREATE | ORDER_EDIT
| PRODUCT_VIEW | PRODUCT_CREATE | PRODUCT_EDIT
// = 0111 0111 = 0x77
hasPermission(opsPerm, ORDER_DELETE) // false
hasPermission(opsPerm, PRODUCT_EDIT) // true
一个整数就能表达两个模块、八种权限的完整组合,存数据库只要一个字段。
和数据库、接口的配合
数据库层面,角色的权限就是一个整数:
1
2
3
4
5
CREATE TABLE roles (
id INT PRIMARY KEY,
name VARCHAR(50),
permissions INT NOT NULL DEFAULT 0
)
后端接口做鉴权的时候,一行位运算就搞定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Express/Koa 中间件示例
function requirePermission(required: number) {
return (req, res, next) => {
const rolePerm = req.user.rolePermissions // 从 session/JWT 里取
if ((rolePerm & required) !== required) {
return res.status(403).json({ message: '权限不足' })
}
next()
}
}
app.delete('/api/orders/:id',
requirePermission(PERM.DELETE),
orderController.delete
)
前端路由守卫也用同样的逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const routes = [
{
path: '/orders',
component: OrderList,
meta: { permission: PERM.VIEW }
},
{
path: '/orders/create',
component: OrderCreate,
meta: { permission: PERM.CREATE }
}
]
router.beforeEach((to) => {
const required = to.meta.permission
if (required && !hasPermission(userStore.permissions, required)) {
return { path: '/403' }
}
})
位掩码方案的边界
这套方案在权限数量不多的时候非常高效,但也有它的适用范围。权限数量在 32 个以内(JS 的位运算上限),一个整数就够用,性能和存储都是最优解。一旦权限超过 32 种,就需要用 BigInt 或者拆成多个字段,这时候不如直接换成权限列表或 RBAC 权限表来管理。
大多数中小业务系统,十几个权限点用位掩码绰绰有余,而且代码里一眼就能看出某个角色到底有哪些权限——把整数转成二进制,哪个 bit 是 1 就对应哪个权限,比翻一堆布尔字段清晰得多。
写在最后
以上内容只是提供一种思路,帮助理解位掩码在权限设计中的工作原理和优势。切勿随意在生产环境方案中照搬使用——实际业务场景往往比示例复杂得多,盲目套用位掩码方案可能会因为错误的使用方式,反而增加代码逻辑复杂度和团队的心智负担。选型之前,先评估业务的权限规模和团队协作成本,合适的方案才是最好的方案。
延伸阅读
位掩码的设计思想在很多看似不相关的领域都有应用。经典面试题”老鼠试毒药”就是同一个思路:每只老鼠代表二进制的一个 bit 位,通过死活状态的组合来定位毒药瓶,本质上就是用 n 个 bit 区分 2^n 种情况。
网络领域的子网掩码也是位掩码的经典应用。255.255.255.0 写成二进制就是 11111111.11111111.11111111.00000000,用 1 的位标识网络部分,用 0 的位标识主机部分,通过位运算 AND 就能快速判断两台机器是否在同一个子网。
这三者的底层逻辑完全相通:用最少的 bit 表达最多的状态,用位运算快速提取或判断。感兴趣可以对照本站的《经典面试题:老鼠试毒药》和《IP 地址:从 IPv4 到 IPv6》两篇文章,体会掩码设计在不同场景下的应用。