1. Краткое содержание
В данной статье в основном обсуждаются следующие два вопроса:
- Побитовые операции в JavaScript: Кратко рассмотрим побитовые операции, я их обычно использую меньше, думаю, многие из них забыли о том же, что и я.
- Дизайн разрешений: в соответствии с характеристиками битовых операций разработайте систему разрешений (добавление, удаление, оценка и т. д.).
2. Побитовые операции JavaScript
2.1. Number
Прежде чем говорить о побитовых операциях, давайте кратко рассмотрим число в JavaScript, которое будет использоваться позже.
В JavaScript числа64-битное число двойной точности с плавающей запятой на основе стандарта IEEE 754., ссылаясь на картинку из Википедии, его структура выглядит так:
- бит знака: используется для обозначения знака
- экспонента: используется для представления мощности
- мантисса (мантисса): используется для обозначения точности
То есть число может находиться в диапазоне от -(2^53-1) до 2^53-1.
Теперь, когда я рассказал об этом здесь, позвольте мне сказать еще одну вещь: 0,1 + 0,2 не является точным из-за этого. Числа с плавающей запятой бесконечны при выражении в двоичном виде и имеют максимум 53 бита и должны быть усечены, что приводит к ошибкам. Самое простое решение — увеличить масштаб на определенное число, кратное целому, а затем уменьшить масштаб после завершения вычисления. Однако безопаснее использоватьmath.jsи другие библиотеки инструментов.
Кроме того, существует четыре основы счисления:
// 十进制
123456789
0
// 二进制:前缀 0b,0B
0b10000000000000000000000000000000 // 2147483648
0b01111111100000000000000000000000 // 2139095040
0B00000000011111111111111111111111 // 8388607
// 八进制:前缀 0o,0O(以前支持前缀 0)
0o755 // 493
0o644 // 420
// 十六进制:前缀 0x,0X
0xFFFFFFFFFFFFFFFFF // 295147905179352830000
0x123456789ABCDEF // 81985529216486900
0XA // 10
Хорошо, это все для Number, давайте посмотрим на битовые операции в JavaScript.
2.2 Битовые операции
Побитовые операторы работают со своими операндами как с 32-битной последовательностью битов (состоящей из 0 и 1), а возвращаемое значение по-прежнему является стандартным значением JavaScript. Побитовые операторы в JavaScript:
оператор | Применение | описывать |
---|---|---|
Побитовое И (И) | a & b |
Для каждого бита результат равен 1, только если соответствующие биты обоих операндов равны 1, иначе он равен 0. |
Побитовое ИЛИ (ИЛИ) | a | b |
Для каждого бита результат равен 1, если хотя бы один из соответствующих битов двух операндов равен 1, и 0 в противном случае. |
Побитовое исключающее или (XOR) | a ^ b |
Для каждого бита результат равен 1, если соответствующий бит двух операндов имеет ровно одну 1, и 0 в противном случае. |
Побитовое НЕ (НЕ) | ~a |
Инвертирует биты операнда, то есть 0 становится 1, а 1 становится 0. |
Сдвиг влево | a << b |
Сдвиньте двоичную форму a на b ( |
подписанный правый сдвиг | a >> b |
Сдвиньте двоичное представление a на b ( |
беззнаковый сдвиг вправо | a >>> b |
Сдвигает двоичное представление a вправо на b ( |
Вот несколько примеров, в основном посмотрите наAND
а такжеOR
:
# 例子1
A = 10001001
B = 10010000
A | B = 10011001
# 例子2
A = 10001001
C = 10001000
A | C = 10001001
# 例子1
A = 10001001
B = 10010000
A & B = 10000000
# 例子2
A = 10001001
C = 10001000
A & C = 10001000
3. Использование битовых операций в системе разрешений
В традиционных системах полномочий существует множество ассоциативных отношений, таких как ассоциация, пользователи, роли пользователей и разрешения. Чем больше система, тем больше взаимосвязей, тем труднее ее поддерживать. Введены битовые вычисления, чтобы быть гениальным решением проблемы.
Прежде чем говорить об «использовании битовых операций в системе разрешений», мы сначала примем две предпосылки:Все обсуждение ниже основано на этих двух предпосылках.:
- Каждый код разрешения уникален (очевидно)
- Двоичная форма всех кодов разрешений, один и только один бит равен 1, а все остальные 0 (
2^n
)
Если полномочия пользователя и код разрешений, используются все вторичные цифровые номера в сочетании с верхними.AND
а такжеOR
Например, анализируя характеристики битовых операций, нетрудно найти:
-
|
может использоваться для предоставления разрешений -
&
Может использоваться для проверки разрешений
Чтобы было понятнее, вот пример анализа в Linux.Права доступа к файлам в Linux делятся на чтение, запись и выполнение, и есть различные выражения, такие как буквы и цифры:
разрешение | алфавитное представление | цифровое представление | бинарный |
---|---|---|---|
читать | r | 4 | 0b100 |
Напишите | w | 2 | 0b010 |
воплощать в жизнь | x | 1 | 0b001 |
Как видите, права доступа 1, 2 и 4 (т.е.2^n
) означает, что после преобразования в двоичный только один бит равен 1, а остальные равны 0. Давайте рассмотрим несколько примеров того, как использовать характеристики двоичного файла для добавления разрешений, контрольной суммы и удаления.
3.1. Добавление разрешений
let r = 0b100
let w = 0b010
let x = 0b001
// 给用户赋全部权限(使用前面讲的 | 操作)
let user = r | w | x
console.log(user)
// 7
console.log(user.toString(2))
// 111
// r = 0b100
// w = 0b010
// r = 0b001
// r|w|x = 0b111
можно увидеть, выполнитьr | w | x
назад,user
Все три бита равны 1, что указывает на все три разрешения.
Когда возникает проблема с правами доступа в Linux, самым грубым решением является
chmod 777 xxx
,здесь7
就代表了:可读,可写,可执行。 Три7
Представляют: владельца файла, владельца группы файла, всех остальных пользователей.
3.2. Проверка разрешений
Только что было продемонстрировано добавление разрешений, а следующее демонстрирует проверку разрешений:
let r = 0b100
let w = 0b010
let x = 0b001
// 给用户赋 r w 两个权限
let user = r | w
// user = 6
// user = 0b110 (二进制)
console.log((user & r) === r) // true 有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
Как и ожидалось, по用户权限 & 权限 code === 权限 code
Вы можете определить, есть ли у пользователя разрешение.
3.3 Удаление разрешений
Мы сказали нам|
дать разрешение, использовать&
Судя по разрешениям, как насчет удаления разрешений? Суть удаления разрешений на самом делесбрасывает 1 в указанной позиции на 0. В предыдущем примере права пользователя0b110
, имеет права на чтение и запись, и теперь я хочу удалить разрешение на чтение, что по сути сбрасывает третью 1 на 0, становясь0b010
:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b010;
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
Итак, как это работает? На самом деле есть два решения, самое простое это XOR^
, в соответствии с приведенным выше введением «Когда соответствующие биты двух операндов имеют и только один 1, результат равен 1, иначе он равен 0», поэтому XOR на самом деле является операцией переключения, если нет увеличения, есть снижаться:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b110 // 有 r w 两个权限
// 执行异或操作,删除 r 权限
user = user ^ r
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 是 0b010
// 再执行一次异或操作
user = user ^ r
console.log((user & r) === r) // true 有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 又变回 0b110
Так что, если вы просто хотите удалить разрешения (вместо того, чтобы увеличивать, если у вас их нет, и уменьшать, если они у вас есть)? Ответ - выполнить&(~code)
, сначала инвертируйте, а затем выполните операцию И:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b110 // 有 r w 两个权限
// 删除 r 权限
user = user & (~r)
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 是 0b010
// 再执行一次
user = user & (~r)
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 还是 0b010,并不会新增
4. Ограничения и решения
Ранее мы рассмотрели числовые и битовые операции в JavaScript и узнали о принципах систем разрешений на основе битовых операций и примерах разрешений файловой системы Linux.
Все вышеперечисленное имеет предпосылки: 1.Каждый код разрешения уникален;2,Двоичная форма каждого кода разрешения, где один и только один бит равен 1 (2^n
). То есть код разрешения может быть только 1, 2, 4, 8,..., 1024,... и, как упоминалось выше, диапазон чисел может быть только между -(2^53-1) и 2^ Между 53 и 1 побитовые операторы JavaScript обрабатывают свои операнды как32 битбитовая последовательность. Тогда количество разрешений, доступных для одного и того же приложения, очень ограничено. Это тоже ограничение программы.
Чтобы преодолеть это ограничение, здесь предлагается концепция под названием «авторизованное пространство», поскольку количество авторизаций ограничено, вы также можете открыть несколько дополнительных пространств для хранения.
Основываясь на пространстве разрешений, мы определяем два формата:
-
код разрешения, строка вида
index,pos
. вpos
Представляет позицию 1 в 32-разрядном двоичном числе (остальные — все 0);index
выражатьпространство разрешений, используемый для преодоления ограничения на количество цифр JavaScript, представляет собой положительное целое число, начинающееся с 0, и каждый код разрешения должен принадлежать пространству разрешений.index
а такжеpos
Разделите их запятыми. -
Права пользователя, строка вида
1,16,16
. запятая разделяет каждыйпространство разрешенийзначение разрешения. Например1,16,16
Это означает, что значение разрешения пространства разрешений 0 равно 1, значение разрешения пространства разрешений 1 равно 16, а значение разрешений пространства разрешений 2 равно 16.
Это может быть непросто понять, поэтому сразу переходите к коду:
// 用户的权限 code
let userCode = ""
// 假设系统里有这些权限
// 纯模拟,正常情况下是按顺序的,如 0,0 0,1 0,2 ...,尽可能占满一个权限空间,再使用下一个
const permissions = {
SYS_SETTING: {
value: "0,0", // index = 0, pos = 0
info: "系统权限"
},
DATA_ADMIN: {
value: "0,8",
info: "数据库权限"
},
USER_ADD: {
value: "0,22",
info: "用户新增权限"
},
USER_EDIT: {
value: "0,30",
info: "用户编辑权限"
},
USER_VIEW: {
value: "1,2", // index = 1, pos = 2
info: "用户查看权限"
},
USER_DELETE: {
value: "1,17",
info: "用户删除权限"
},
POST_ADD: {
value: "1,28",
info: "文章新增权限"
},
POST_EDIT: {
value: "2,4",
info: "文章编辑权限"
},
POST_VIEW: {
value: "2,19",
info: "文章查看权限"
},
POST_DELETE: {
value: "2,26",
info: "文章删除权限"
}
}
// 添加权限
const addPermission = (userCode, permission) => {
const userPermission = userCode ? userCode.split(",") : []
const [index, pos] = permission.value.split(",")
userPermission[index] = (userPermission[index] || 0) | Math.pow(2, pos)
return userPermission.join(",")
}
// 删除权限
const delPermission = (userCode, permission) => {
const userPermission = userCode ? userCode.split(",") : []
const [index, pos] = permission.value.split(",")
userPermission[index] = (userPermission[index] || 0) & (~Math.pow(2, pos))
return userPermission.join(",")
}
// 判断是否有权限
const hasPermission = (userCode, permission) => {
const userPermission = userCode ? userCode.split(",") : []
const [index, pos] = permission.value.split(",")
const permissionValue = Math.pow(2, pos)
return (userPermission[index] & permissionValue) === permissionValue
}
// 列出用户拥有的全部权限
const listPermission = userCode => {
const results = []
if (!userCode) {
return results
}
Object.values(permissions).forEach(permission => {
if (hasPermission(userCode, permission)) {
results.push(permission.info)
}
})
return results
}
const log = () => {
console.log(`userCode: ${JSON.stringify(userCode, null, " ")}`)
console.log(`权限列表: ${listPermission(userCode).join("; ")}`)
console.log("")
}
userCode = addPermission(userCode, permissions.SYS_SETTING)
log()
// userCode: "1"
// 权限列表: 系统权限
userCode = addPermission(userCode, permissions.POST_EDIT)
log()
// userCode: "1,,16"
// 权限列表: 系统权限; 文章编辑权限
userCode = addPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1073741825,,16"
// 权限列表: 系统权限; 用户编辑权限; 文章编辑权限
userCode = addPermission(userCode, permissions.USER_DELETE)
log()
// userCode: "1073741825,131072,16"
// 权限列表: 系统权限; 用户编辑权限; 用户删除权限; 文章编辑权限
userCode = delPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1,131072,16"
// 权限列表: 系统权限; 用户删除权限; 文章编辑权限
userCode = delPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1,131072,16"
// 权限列表: 系统权限; 用户删除权限; 文章编辑权限
userCode = delPermission(userCode, permissions.USER_DELETE)
userCode = delPermission(userCode, permissions.SYS_SETTING)
userCode = delPermission(userCode, permissions.POST_EDIT)
log()
// userCode: "0,0,0"
// 权限列表:
userCode = addPermission(userCode, permissions.SYS_SETTING)
log()
// userCode: "1,0,0"
// 权限列表: 系统权限
кроме как путем введенияпространство разрешенийКонцепцию преодоления разрядного предела бинарных операций можно также использоватьmath.jsизbignumber
, напрямую работают с двоичными числами длиной более 32 бит, подробности можно посмотреть в документации, и я не буду здесь вдаваться в подробности.
5. Применимые сценарии и проблемы
Согласно наиболее распространенномуRBACМодель системы разрешений проектирования, то есть вообще несколько сущностей: приложение, разрешение, роль, пользователь. Разрешения пользователей могут исходить непосредственно от разрешений или от ролей:
- Несколько разрешений в одном приложении
- Разрешения и роли находятся в отношениях «многие ко многим»
- Пользователи и роли находятся в отношениях «многие ко многим».
- Пользователи и разрешения находятся в отношениях «многие ко многим».
В этой модели существует общая связь между пользователем и соответствующими правами доступа к таблице, пользователями и ролями, ролями и разрешениями. Представьте систему управления правами за кулисами торгового центра, там могут быть десятки тысяч, а то и сотни тысяч магазинов (приложений), у каждого магазина могут быть десятки пользователей, ролей, разрешений. С непрерывным развитием бизнеса и только что упомянутой таблицы трех соответствий будет все труднее поддерживать ее.
Метод преобразования таблицы бинарных соответствий можно не указывать, чтобы сократить запрос, сэкономить место. Конечно, переписка не опускается без вреда, например, следующие несколько проблем:
- Как эффективно найти мои разрешения?
- Как эффективно найти всех пользователей с определенным разрешением?
- Как контролировать срок действия разрешений?
Таким образом, базовая схема преобразования больше подходит для сценариев, когда существует много только что упомянутых приложений, и каждое приложение имеет небольшое количество пользователей, разрешений и ролей.
6. Другие варианты
Помимо бинарной схемы, конечно, есть и другие схемы, позволяющие добиться подобных эффектов, например, непосредственно используется строка, состоящая из 1 и 0. Точка разрешения соответствует индексу, 1 означает наличие разрешения, а 0 означает отсутствует разрешение. Например: добавить 0, удалить 1, изменить 2, пользователь А имеет разрешение на добавление и редактирование, тогда userCode — 101; пользователь B имеет все разрешения, userCode — 111. Эта схема проще, чем двоичное преобразование, но занимает много места.
Существует также схема с использованием простых чисел: все точки разрешений являются простыми числами, а права пользователя — это произведение всех имеющихся у них точек разрешений. Например: точки разрешения 2, 3, 5, 7, 11, а разрешение пользователя 5 * 7 * 11 = 385. Проблемной частью этой схемы является получение простых чисел (добавление точек доступа) и простой факторизации (оценка разрешений). Когда точек доступа слишком много, это почти станет RSA. Если добавить, удалить, изменить только несколько разрешений, и проверено, его можно считать.
7. Ссылка
- MDN: числа и даты JavaScript
- Тип с плавающей запятой двойной точности
- MDN: побитовые операторы
- [Маленькие знания и большая правда] Забытая битовая операция
- Почему бы не использовать побитовые операторы в JavaScript?
- 100 видов ролевых привилегий дизайнерских решений
- Обзор системы разрешений и модели RBAC
- Дизайн и алгоритм авторизации
- управление доступом на основе ролей
Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы всегда нанимаем, если вы готовы сменить работу и вам нравится облачная музыка, тоПрисоединяйтесь к нам!