Рубрики
Без рубрики

Введение в ACL Spring Security

Быстрое и практическое введение в ACL Spring Security.

Автор оригинала: baeldung.

1. введение

Список управления доступом ( ACL) – это список разрешений, прикрепленных к объекту. ACL указывает, какие идентификаторы предоставляются для каких операций с данным объектом.

Spring Security Список контроля доступа is a Spring компонент, который поддерживает Безопасность объектов домена. Проще говоря, Spring ACL помогает в определении разрешений для конкретного пользователя/роли на одном объекте домена, а не по всем направлениям, на типичном уровне для каждой операции.

Например, пользователь с ролью Администратор может видеть ( ЧИТАТЬ) и редактировать ( ПИСАТЬ) все сообщения в Центральном окне уведомлений , но обычный пользователь может видеть только сообщения, относиться к ним и не может редактировать. Тем временем другие пользователи с ролью Редактор могут просматривать и редактировать некоторые конкретные сообщения.

Следовательно, разные пользователи/роли имеют разные разрешения для каждого конкретного объекта. В этом случае Spring ACL способен выполнить поставленную задачу. В этой статье мы рассмотрим, как настроить базовую проверку разрешений с помощью Spring ACL .

2. Конфигурация

2.1. База данных ACL

Чтобы использовать Spring Security ACL , нам нужно создать четыре обязательные таблицы в нашей базе данных.

Первая таблица – ACL_CLASS , в которой хранится имя класса объекта домена, столбцы включают:

  • ИДЕНТИФИКАТОР
  • КЛАСС: имя класса объектов защищенного домена, например: com.baeldung.acl.persistence.entity.Сообщение с уведомлением

Во-вторых, нам нужна таблица ACL_SID , которая позволяет нам универсально идентифицировать любой принцип или авторитет в системе. Стол нуждается в:

  • ИДЕНТИФИКАТОР
  • SID: это имя пользователя или роли. SID расшифровывается как Идентификатор безопасности
  • ПРИНЦИПАЛ: 0 или 1 , чтобы указать, что соответствующий SID является основным (пользователь, например мэри, Майк, джек… ) или полномочным (роль, например ROLE_ADMIN, ROLE_USER, ROLE_EDITOR… )

Следующая таблица – ACL_OBJECT_IDENTITY, в которой хранится информация для каждого уникального объекта домена:

  • ИДЕНТИФИКАТОР
  • OBJECT_ID_CLASS: определение класса объектов домена, | ссылки на ACL_CLASS таблица OBJECT_ID_IDENTITY:
  • объекты домена могут храниться во многих таблицах в зависимости от класса. Следовательно, в этом поле хранится первичный ключ целевого объекта PARENT_OBJECT:
  • укажите родителя этого идентификатора объекта в этой таблице OWNER_SID:
  • ID владельца объекта, ссылки на ACL_SID таблицу ENTRIES_INHERITING:
  • наследуются ли Записи ACL этого объекта от родительского объекта ( Записи ACL определены в ACL_ENTRY таблице)

Наконец, ACL_ENTRY хранит индивидуальные разрешения, назначаемые каждой СТОРОНЕ на идентификаторе объекта :

  • ИДЕНТИФИКАТОР
  • ACL_OBJECT_IDENTITY: укажите идентификатор объекта, ссылки на ACL_OBJECT_IDENTITY таблица
  • ACE_ORDER: порядок текущей записи в записях ACL список соответствующих Идентификаторов объектов
  • СТОРОНА: целевая СТОРОНА , которой предоставлено или отказано в разрешении, ссылки на ACL_SIDE таблицу
  • МАСКА: целочисленная битовая маска, представляющая фактическое разрешение, предоставленное или отклоненное
  • ПРЕДОСТАВЛЕНИЕ: значение 1 означает предоставление, значение 0 означает отрицание
  • AUDIT_SUCCESS и AUDIT_FAILURE : для целей аудита

2.2. Зависимость

Чтобы иметь возможность использовать Spring ACL в нашем проекте, давайте сначала определим наши зависимости:


    org.springframework.security
    spring-security-acl


    org.springframework.security
    spring-security-config


    org.springframework
    spring-context-support


    net.sf.ehcache
    ehcache-core
    2.6.11

Spring ACL требует кэша для хранения идентификатора объекта и записей ACL , поэтому мы будем использовать Ehcache здесь. И, чтобы поддержать Ehcache в Spring, нам также нужна spring-context-поддержка.

Когда мы не работаем с Spring Boot, нам нужно явно добавлять версии. Их можно проверить на Maven Central: spring-security-acl , spring-security-config , spring-context-support , ehcache-core .

2.3. Конфигурация, связанная с ACL

Нам нужно защитить все методы, которые возвращают защищенные объекты домена, или внести изменения в объект, включив Глобальную безопасность методов:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AclMethodSecurityConfiguration 
  extends GlobalMethodSecurityConfiguration {

    @Autowired
    MethodSecurityExpressionHandler 
      defaultMethodSecurityExpressionHandler;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return defaultMethodSecurityExpressionHandler;
    }
}

Давайте также включим Управление доступом на основе выражений , установив prePostEnabled в true для использования Spring Expression Language (SpEL) . Кроме того , нам нужен обработчик выражений с поддержкой ACL :

@Bean
public MethodSecurityExpressionHandler 
  defaultMethodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler
      = new DefaultMethodSecurityExpressionHandler();
    AclPermissionEvaluator permissionEvaluator 
      = new AclPermissionEvaluator(aclService());
    expressionHandler.setPermissionEvaluator(permissionEvaluator);
    return expressionHandler;
}

Следовательно, , мы назначаем AclPermissionEvaluator для defaultmethod securityexpressionhandler . Оценщику требуется MutableAclService для загрузки параметров разрешений и определений объектов домена из базы данных.

Для простоты мы используем предоставленный JdbcMutableAclService :

@Bean 
public JdbcMutableAclService aclService() { 
    return new JdbcMutableAclService(
      dataSource, lookupStrategy(), aclCache()); 
}

В качестве своего имени/| JdbcMutableAclService использует JdbcTemplate для упрощения доступа к базе данных. Ему нужен Источник данных ( для JdbcTemplate) , Стратегия поиска (обеспечивает оптимизированный поиск при запросе базы данных) и AclCache ( кэширование ACL | Записи и Идентификатор объекта) .

Опять же, для простоты мы используем предоставленные BasicLookupStrategy и EhCacheBasedAclCache .

@Autowired
DataSource dataSource;

@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
    return new AclAuthorizationStrategyImpl(
      new SimpleGrantedAuthority("ROLE_ADMIN"));
}

@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
    return new DefaultPermissionGrantingStrategy(
      new ConsoleAuditLogger());
}

@Bean
public EhCacheBasedAclCache aclCache() {
    return new EhCacheBasedAclCache(
      aclEhCacheFactoryBean().getObject(), 
      permissionGrantingStrategy(), 
      aclAuthorizationStrategy()
    );
}

@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
    EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
    ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
    ehCacheFactoryBean.setCacheName("aclCache");
    return ehCacheFactoryBean;
}

@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
    return new EhCacheManagerFactoryBean();
}

@Bean 
public LookupStrategy lookupStrategy() { 
    return new BasicLookupStrategy(
      dataSource, 
      aclCache(), 
      aclAuthorizationStrategy(), 
      new ConsoleAuditLogger()
    ); 
}

Здесь AclAuthorizationStrategy отвечает за заключение о том, обладает ли текущий пользователь всеми необходимыми разрешениями на определенные объекты или нет.

Он нуждается в поддержке PermissionGrantingStrategy, которая определяет логику определения того, предоставляется ли разрешение конкретному SID .

3. Безопасность метода С Пружинным ACL

До сих пор мы выполнили все необходимые настройки . Теперь мы можем установить обязательное правило проверки для наших защищенных методов.

По умолчанию Spring ACL ссылается на класс BasePermission для всех доступных разрешений. В принципе, у нас есть разрешение на ЧТЕНИЕ, ЗАПИСЬ, СОЗДАНИЕ, УДАЛЕНИЕ и АДМИНИСТРИРОВАНИЕ .

Давайте попробуем определить некоторые правила безопасности:

@PostFilter("hasPermission(filterObject, 'READ')")
List findAll();
    
@PostAuthorize("hasPermission(returnObject, 'READ')")
NoticeMessage findById(Integer id);
    
@PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")
NoticeMessage save(@Param("noticeMessage")NoticeMessage noticeMessage);

После выполнения метода findAll() будет запущен @PostFilter . Требуемое правило hasPermission(filterObject, ‘READ’), означает возврат только тех Сообщений уведомления , на которые текущий пользователь имеет разрешение на ЧТЕНИЕ .

Аналогично, @PostAuthorize запускается после выполнения метода findById () , убедитесь, что объект Notice Message возвращается только в том случае, если текущий пользователь имеет на него разрешение READ . Если нет, система выдаст исключение AccessDeniedException .

С другой стороны, система запускает аннотацию @PreAuthorize перед вызовом метода save () . Он будет решать, где разрешено выполнять соответствующий метод или нет. Если нет, будет выдано исключение AccessDeniedException .

4. В действии

Теперь мы проверим все эти конфигурации с помощью JUnit . Мы будем использовать H2 database, чтобы максимально упростить настройку.

Нам нужно будет добавить:


  com.h2database
  h2



  org.springframework
  spring-test
  test



  org.springframework.security
  spring-security-test
  test

4.1. Сценарий

В этом сценарии у нас будет два пользователя ( менеджер, hr) и одна роль пользователя ( ROLE_EDITOR), поэтому ваш acl_sid будет:

INSERT INTO acl_sid (id, principal, sid) VALUES
  (1, 1, 'manager'),
  (2, 1, 'hr'),
  (3, 0, 'ROLE_EDITOR');

Затем нам нужно объявить Notice Message class в acl_class . И три экземпляра Notice Message class будут вставлены в system_message.

Кроме того, соответствующие записи для этих 3 экземпляров должны быть объявлены в acl_object_identity :

INSERT INTO acl_class (id, class) VALUES
  (1, 'com.baeldung.acl.persistence.entity.NoticeMessage');

INSERT INTO system_message(id,content) VALUES 
  (1,'First Level Message'),
  (2,'Second Level Message'),
  (3,'Third Level Message');

INSERT INTO acl_object_identity 
  (id, object_id_class, object_id_identity, 
  parent_object, owner_sid, entries_inheriting) 
  VALUES
  (1, 1, 1, NULL, 3, 0),
  (2, 1, 2, NULL, 3, 0),
  (3, 1, 3, NULL, 3, 0);

Первоначально мы предоставляем ЧТЕНИЕ и ЗАПИСЬ разрешения на первый объект ( id ) пользователю менеджеру . Между тем, любой пользователь с ROLE_EDITOR будет иметь разрешение на ЧТЕНИЕ для всех трех объектов, но будет иметь только разрешение на ЗАПИСЬ для третьего объекта ( id=3 ). Кроме того, пользователь hr будет иметь разрешение только READ на второй объект.

Здесь, поскольку мы используем по умолчанию Spring ACL | Базовое разрешение класс для проверки разрешений, максимальное значение разрешения READ будет равно 1, а значение маски разрешения WRITE будет равно 2. Наши данные в acl_entry будут:

INSERT INTO acl_entry 
  (id, acl_object_identity, ace_order, 
  sid, mask, granting, audit_success, audit_failure) 
  VALUES
  (1, 1, 1, 1, 1, 1, 1, 1),
  (2, 1, 2, 1, 2, 1, 1, 1),
  (3, 1, 3, 3, 1, 1, 1, 1),
  (4, 2, 1, 2, 1, 1, 1, 1),
  (5, 2, 2, 3, 1, 1, 1, 1),
  (6, 3, 1, 3, 1, 1, 1, 1),
  (7, 3, 2, 3, 2, 1, 1, 1);

4.2. Тестовый случай

Прежде всего, мы пытаемся вызвать метод findAll .

В нашей конфигурации метод возвращает только те Уведомления , на которые у пользователя есть разрешение на ЧТЕНИЕ .

Следовательно, мы ожидаем, что список результатов содержит только первое сообщение:

@Test
@WithMockUser(username = "manager")
public void 
  givenUserManager_whenFindAllMessage_thenReturnFirstMessage(){
    List details = repo.findAll();
 
    assertNotNull(details);
    assertEquals(1,details.size());
    assertEquals(FIRST_MESSAGE_ID,details.get(0).getId());
}

Затем мы пытаемся вызвать тот же метод с любым пользователем, у которого есть роль – ROLE_EDITOR . Обратите внимание, что в этом случае эти пользователи имеют разрешение READ на все три объекта.

Следовательно, мы ожидаем, что список результатов будет содержать все три сообщения:

@Test
@WithMockUser(roles = {"EDITOR"})
public void 
  givenRoleEditor_whenFindAllMessage_thenReturn3Message(){
    List details = repo.findAll();
    
    assertNotNull(details);
    assertEquals(3,details.size());
}

Затем, используя manager user, мы попытаемся получить первое сообщение по идентификатору и обновить его содержимое – все должно работать нормально:

@Test
@WithMockUser(username = "manager")
public void 
  givenUserManager_whenFind1stMessageByIdAndUpdateItsContent_thenOK(){
    NoticeMessage firstMessage = repo.findById(FIRST_MESSAGE_ID);
    assertNotNull(firstMessage);
    assertEquals(FIRST_MESSAGE_ID,firstMessage.getId());
        
    firstMessage.setContent(EDITTED_CONTENT);
    repo.save(firstMessage);
        
    NoticeMessage editedFirstMessage = repo.findById(FIRST_MESSAGE_ID);
 
    assertNotNull(editedFirstMessage);
    assertEquals(FIRST_MESSAGE_ID,editedFirstMessage.getId());
    assertEquals(EDITTED_CONTENT,editedFirstMessage.getContent());
}

Но если какой – либо пользователь с ролью ROLE_EDITOR обновит содержимое первого сообщения-наша система выдаст исключение AccessDeniedException :

@Test(expected = AccessDeniedException.class)
@WithMockUser(roles = {"EDITOR"})
public void 
  givenRoleEditor_whenFind1stMessageByIdAndUpdateContent_thenFail(){
    NoticeMessage firstMessage = repo.findById(FIRST_MESSAGE_ID);
 
    assertNotNull(firstMessage);
    assertEquals(FIRST_MESSAGE_ID,firstMessage.getId());
 
    firstMessage.setContent(EDITTED_CONTENT);
    repo.save(firstMessage);
}

Аналогично, пользователь hr может найти второе сообщение по идентификатору, но не сможет его обновить:

@Test
@WithMockUser(username = "hr")
public void givenUsernameHr_whenFindMessageById2_thenOK(){
    NoticeMessage secondMessage = repo.findById(SECOND_MESSAGE_ID);
    assertNotNull(secondMessage);
    assertEquals(SECOND_MESSAGE_ID,secondMessage.getId());
}

@Test(expected = AccessDeniedException.class)
@WithMockUser(username = "hr")
public void givenUsernameHr_whenUpdateMessageWithId2_thenFail(){
    NoticeMessage secondMessage = new NoticeMessage();
    secondMessage.setId(SECOND_MESSAGE_ID);
    secondMessage.setContent(EDITTED_CONTENT);
    repo.save(secondMessage);
}

5. Заключение

В этой статье мы рассмотрели базовую конфигурацию и использование Spring ACL .

Как мы знаем, Spring ACL требовал специальных таблиц для управления объектом, принципом/полномочиями и настройкой разрешений. Все взаимодействия с этими таблицами, особенно действия по обновлению, должны проходить через службу Acl. Мы рассмотрим этот сервис для базовых CRUD действий в будущей статье.

По умолчанию мы ограничены предопределенным разрешением в классе Basepermission n.

Наконец, реализацию этого учебника можно найти на Github .