Управление разрешениями на внешнем интерфейсе предназначено только для взаимодействия с пользователем, соответствующая роль отображает соответствующее представление, а реальная безопасность находится на заднем плане.
предисловие
В начале выпуска основное содержание работы заключалось в разработке системы фонового управления Явлением, существовавшим в то время, было:
Если пользователь помнит определенный URL-адрес и напрямую вводит его в браузере, он или она может войти на страницу независимо от того, есть ли у пользователя разрешение на доступ к странице.
Если страница инициализируется (componentDidMount
) для выполнения запроса интерфейса серверная часть вернет код состояния HTTP 403, а интерфейсная часть инкапсулируетrequest.js
Исключения, не связанные с бизнесом, будут обработаны, и если будет обнаружена ошибка 403, она будет перенаправлена на неавторизованную страницу.
Если при инициализации страницы нет внешнего и внутреннего взаимодействия, описанный выше процесс не будет запущен до тех пор, пока пользователь не инициирует определенные действия (например, отправку формы).
Видно, что гарантия безопасности — это задняя часть, так что же может сделать передняя часть?
- Четко информируйте пользователей о том, что у них нет разрешений, чтобы пользователи ошибочно не думали, что у них есть разрешение на работу (даже если операция не удалась), и сразу переходили на страницу без разрешений;
- Перехватывать четкие и неавторизованные запросы, такие как записи определенных операций (кнопки или навигация и т. д.), которые требуют разрешений и не отображаются неавторизованным пользователям.Фактически этот пункт включает в себя предыдущий пункт.
Я смотрел его недавноAnt Design Pro
Необходимо сделать сводку обработки, связанной с разрешениями.
Следует отметить, что, хотя данная статья основана наAnt Design Pro
Идея дизайна разрешения не является полной интерпретацией его исходного кода (может быть более склонна к идее v1, без участия umi).
Если есть ошибки и недоразумения, пожалуйста, шлепните и исправьте их, спасибо.
Обработка разрешений на уровне модуля
Предположим, что существует следующая связь:
роль роль | Полномочия перечисления значение авторитета | логика |
---|---|---|
обычный пользователь | user | Не показывай |
администратор | admin | Отображение кнопки «Войти в админку Backstage» |
На определенной странице есть кнопка с текстом "Войти в фон управления", которая видна только администратору, давайте реализуем.
Простая реализация
// currentAuthority 为当前用户权限枚举值
const AdminBtn = ({ currentAuthority }) => {
if ('admin' === currentAuthority) {
return <button>进入管理后台</button>;
}
return null;
};
Ну, это супер просто.
контроль доступа естьif else
, реализация функции не сложна, главное, что обработка каждой страницы|модуля|кнопки должна оцениваться один раз, и требования всегда могут быть реализованы.
Однако теперь это просто кнопка на странице, и мы столкнемся со многими сценариями, когда «определенный xxx существует на определенной странице (несколько) и отображается только для xxx (или/и xxx)».
Значит, может лучше.
Ниже показан один из самых основных компонентов управления разрешениями.Authorized
.
Инкапсуляция компонентов — разрешено
Ожидаемая форма вызова выглядит следующим образом:
<Authorized currentAuthority={currentAuthority} authority={'admin'} noMatch={null}>
<button>进入管理后台</button>
</Authorized>
Апи выглядит следующим образом:
параметр | иллюстрировать | Типы | По умолчанию |
---|---|---|---|
children | Элементы, которые отображаются нормально, отображаются при вынесении решения о разрешении | ReactNode | |
currentAuthority | текущие разрешения | string | |
authority | права доступа | string/string[] | |
noMatch | Отображается, когда решение о разрешении не принято | ReactNode |
currentAuthority
Нет необходимости передавать это свойство вручную каждый раз при его вызове, здесь предполагается, что информация о пользователе передается черезredux
Получить и хранить по всему мируstore
середина.
Примечание. Конечно, мы можем повесить информацию о пользователе вwindow
вниз илиlocalStorage
Однако очень важно, что в большинстве сценариев мы получаем данные асинхронно через интерфейс, что очень важно. еслиhtml
размещенный на бэкэнде илиssr
В случае с сервером информация о пользователе вводится непосредственно на сервер, что действительно хорошо.
новыйsrc/components/Authorized/Authorized.jsx
Реализация выглядит следующим образом:
import { connect } from 'react-redux';
function Authorized(props) {
const { children, userInfo, authority, noMatch } = props;
const { currentAuthority } = userInfo || {};
if (!authority) return children;
const _authority = Array.isArray(authority) ? authority : [authority];
if (_authority.includes(currentAuthority)) return children;
return noMatch;
}
export default connect(store => ({ userInfo: store.common.userInfo }))(Authorized);
Теперь нам не нужно вручную передаватьcurrentAuthority
:
<Authorized authority={'admin'} noMatch={null}>
<button>进入管理后台</button>
</Authorized>
✨ Отлично, мы делаем первые шаги.
существует
Ant Design Pro
в, дляcurrentAuthority
(текущие разрешения) сauthority
(Разрешение на доступ) функция сопоставления, которая определяетcheckPermissions
метод, обеспечивающий различные формы сопоставления, в этой статье обсуждаются толькоauthority
в виде массива (множественные права доступа) или строки (одиночные права доступа),currentAuthority
Когда это строка (текущая роль имеет только одно разрешение).
Обработка разрешений на уровне страницы
страница размещенаRoute
Модули в разделе Компоненты.
Зная это, мы можем легко написать следующий код:
новыйsrc/router/index.jsx
, который отображается, когда роль пользователя не соответствует маршрутуRedirect
Компоненты используются для перенаправления.
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import NormalPage from '@/views/NormalPage'; /* 公开页面 */
import UserPage from '@/views/UserPage'; /* 普通用户和管理员均可访问的页面*/
import AdminPage from '@/views/AdminPage'; /* 管理员才可访问的页面*/
import Authorized from '@/components/Authorized';
// Layout就是一个布局组件,写一些公用头部底部啥的
function Router() {
return (
<BrowserRouter>
<Layout>
<Switch>
<Route exact path='/' component={NormalPage} />
<Authorized
authority={['admin', 'user']}
noMatch={
<Route path='/user-page' render={() => <Redirect to={{ pathname: '/login' }} />} />
}
>
<Route path='/user-page' component={UserPage} />
</Authorized>
<Authorized
authority={'admin'}
noMatch={
<Route path='/admin-page' render={() => <Redirect to={{ pathname: '/403' }} />} />
}
>
<Route path='/admin-page' component={AdminPage} />
</Authorized>
</Switch>
</Layout>
</BrowserRouter>
);
}
export default Router;
Этот код не работает, так как текущая информация о правах доступа получается асинхронно через интерфейс, в это времяAuthorized
Компонент не может получить текущее разрешение (currentAuthority
), при доступе напрямую через URL/user-page
или/admin-page
, независимо от того, совпадает ли личность пользователя, результат запроса не будет возвращен, он будет перенаправлен на/login
или/403
, этот вопрос будет обсуждаться позже.
Давайте сначала оптимизируем наш код.
Извлечь конфигурацию маршрутизации
Слишком много jsx-контента, связанного с конфигурацией маршрутизации, слишком много страниц сложно поддерживать, читабельность сильно снижена Мы можем извлечь конфигурацию маршрутизации.
новыйsrc/router/router.config.js
, который специально используется для хранения информации о конфигурации, связанной с маршрутизацией.
import NormalPage from '@/views/NormalPage';
import UserPage from '@/views/UserPage';
import AdminPage from '@/views/AdminPage';
export default [
{
exact: true,
path: '/',
component: NormalPage,
},
{
path: '/user-page',
component: UserPage,
authority: ['user', 'admin'],
redirectPath: '/login',
},
{
path: '/admin-page',
component: AdminPage,
authority: ['admin'],
redirectPath: '/403',
},
];
Инкапсуляция компонентов — AuthorizedRoute
Далее на основеAuthorized
пара компонентовRoute
Компоненты переупакованы.
новыйsrc/components/Authorized/AuthorizedRoute.jsx
.
Реализация выглядит следующим образом:
import React from 'react';
import { Route } from 'react-router-dom';
import Authorized from './Authorized';
function AuthorizedRoute({ component: Component, render, authority, redirectPath, ...rest }) {
return (
<Authorized
authority={authority}
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
>
<Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
</Authorized>
);
}
export default AuthorizedRoute;
Оптимизировано
Теперь перепишите наш компонент Router.
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import AuthorizedRoute from '@/components/AuthorizedRoute';
import routeConfig from './router.config.js';
function Router() {
return (
<BrowserRouter>
<Layout>
<Switch>
{routeConfig.map(rc => {
const { path, component, authority, redirectPath, ...rest } = rc;
return (
<AuthorizedRoute
key={path}
path={path}
component={component}
authority={authority}
redirectPath={redirectPath}
{...rest}
/>
);
})}
</Switch>
</Layout>
</BrowserRouter>
);
}
export default Router;
Чувствую себя намного лучше.
Но все еще есть проблема - поскольку информация о правах пользователя получается асинхронно, до того, как будут возвращены данные информации о правах доступа,AuthorizedRoute
компонент подталкивает пользователя кredirectPath
.
фактически
Ant Design Pro
версии v4 имеют эту проблему, по сравнению с v2@/pages/Authorized
компоненты изlocalStorage
Информация о разрешениях получается из v4, а данные вместо этого получаются из редукса (данные в редуксе получаются через интерфейс), что аналогично этой статье. конкретный видимыйЭтот пиар.
Получить разрешения асинхронно
Решение очень простое: убедитесь, что информация о правах пользователя уже существует в избыточности, когда соответствующие компоненты разрешений смонтированы. Другими словами, после возврата данных интерфейса выполняется соответствующий рендеринг.
Мы можем получить информацию о пользователе в Layout и отрендерить после получения данныхchildren
.
Эпилог
Ant Design Pro
Начиная с v2 нижний слой основан наumi
Реализация, настроенная маршрутизациейRoutes
свойства, комбинированные@/pages/Authorized
компонент (основанный на@/utils/Authorized
Компоненты -@/components/Authorized
вторичная инкапсуляция, инъекцияcurrentAuthority
(текущие разрешения)) для реализации основного процесса. В то же время информация о разрешениях хранится вlocalStorage
,пройти через@/utils/authority.js
Предоставленные инструменты и методы для разрешенияget
так же какset
.
Смотри внимательно@/components/Authorized
Содержимое файла также содержалоAuthorizedRoute
компонент, но в коде не используется (вместо@/pages/Authorized
компонент), я просмотрел вопрос и узнал, что v1 не основан наumi
, на основеAuthorizedRoute
Для управления полномочиями маршрутизации после обновленияAuthorizedRoute
Он не используется для управления разрешениями маршрутизации.
Задействовано много связанных файлов (components/pages/utils), а документации по версии 4 немного не хватает.Если вы посмотрите на исходный код, если вы не разберетесь с различиями между версиями, это будет очень трудоемко.
В этой статье информация о разрешениях получается асинхронно через интерфейс и сохраняется в редуксе (аналогично версии v4, см.@/pages/Authorized
так же как@/layouts/SecurityLayout
).