Управление разрешениями на внешнем интерфейсе предназначено только для взаимодействия с пользователем, соответствующая роль отображает соответствующее представление, а реальная безопасность находится на заднем плане.
предисловие
В начале выпуска основное содержание работы заключалось в разработке системы фонового управления Явлением, существовавшим в то время, было:
Если пользователь помнит определенный 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).