Динамическое управление доступом

Динамическое управление доступом

Я хотел бы поделиться инсайтами, полученными в ходе реализации функции в одном из проектов в прошлом году, которая позволяла администраторам напрямую управлять правами доступа к каждому ресурсу. От обновления прав без прерывания сервиса до проектирования базы данных — рассмотрим пошагово, как эффективно реализовать динамическую авторизацию на реальном примере.


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