Динамическое управление доступом
Я хотел бы поделиться инсайтами, полученными в ходе реализации функции в одном из проектов в прошлом году, которая позволяла администраторам напрямую управлять правами доступа к каждому ресурсу. От обновления прав без прерывания сервиса до проектирования базы данных — рассмотрим пошагово, как эффективно реализовать динамическую авторизацию на реальном примере.
1. Проектирование домена — структура базы данных для динамической авторизации
Домен Menu, предназначенный для поддержки динамической авторизации, имеет следующую структуру:
public class Menu {
private String id;
private String menuContext; // например: admin:project:button
private List<String> permittedRoleCodes; // роли, которым разрешен доступ к данному меню
}
Поле menuContext чётко определяет иерархическую структуру сервиса. Кроме того, данные о разрешениях, хранящиеся в permittedRoleCodes, создают основу для гибкой авторизации во время выполнения (runtime).
2. Backend — реализация динамической авторизации с использованием Spring AOP
Была создана кастомная аннотация (@PermissionCheckRequired) и Aspect для её обработки. Ключевая идея данного подхода — полное разделение бизнес-логики и логики авторизации. PermissionCheckRequiredAspect отвечает только за извлечение значений аннотации, а фактическая проверка прав делегируется внешнему компоненту permissionConsumer. Такой подход снижает связанность и повышает удобство сопровождения.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheckRequired {
String menuContext();
}
@Aspect
@RequiredArgsConstructor
public class PermissionCheckRequiredAspect {
@Around("@annotation({packageName}.PermissionCheckRequired)")
public Object checkPermission(ProceedingJoinPoint point) throws Throwable {
// логика извлечения значений аннотации
}
}
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
@RequiredArgsConstructor
public class PermissionConfig {
private final ObjectProvider<Object> beanProvider;
private final UserSeek userSeek;
@Bean
public PermissionCheckRequiredAspect permissionCheckRequiredAspect() {
return new PermissionCheckRequiredAspect(menuContext -> {
// логика проверки прав доступа
});
}
}
Теперь достаточно добавить эту аннотацию к API-методам, требующим контроля доступа. Это избавляет от необходимости повторно писать сложную логику проверки прав в сервисном слое.
@PostMapping("/sync-users/command")
@PermissionCheckRequired(menuContext = "admin:user:sync")
public CommandResponse syncUsers(@RequestBody SyncUsersCommand command) {
// бизнес-логика
}
3. Frontend — регистрация меню и контроль доступа с помощью компонентов
На фронтенде важно показывать пользователю только те функции, к которым у него есть доступ, чтобы уменьшить путаницу и предотвратить проблемы с безопасностью. Для этого был создан переиспользуемый компонент (WithPermission), который управляет рендерингом на основе прав доступа:
export const WithPermission = ({
children,
menuContext
}: {
children: ReactNode;
menuContext: string;
}) => {
const RenderByRoles = () => {
// логика проверки прав
};
return (
<RenderByRoles />
);
};
Этот компонент может оборачивать как всю страницу, так и отдельные элементы интерфейса (например, кнопки). Он использует те же значения menuContext, что и в backend-аннотациях, обеспечивая согласованность.
<WithPermission menuContext='admin:menu'>
<MenuPageView />
</WithPermission>
Кроме того, с помощью скриптов статического анализа можно автоматически извлекать и регистрировать информацию о меню из проекта.
function extractWithPermissionProps(filePaths: string[]): MenuCdo[] {
for (const filePath of filePaths) {
const content = fs.readFileSync(filePath, 'utf-8');
const regex = /<WithPermission...>/g; // извлечение информации о меню
let match;
while ((match = regex.exec(content)) !== null) {
// регистрация извлеченного меню
}
}
}
4. Заключение — преимущества гибкого управления доступом
Мы рассмотрели полный процесс реализации системы динамической авторизации: от проектирования базы данных до backend AOP и автоматизации на frontend с помощью статического анализа. Ранее любые изменения в контроле доступа к меню требовали изменения кода и повторного развертывания. Теперь же права доступа можно обновлять в реальном времени, что позволяет оперативно реагировать на изменения без прерывания работы сервиса.
Hustle Paul