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

Пользовательский Accessdecisionvoter в Spring Security

Краткое и практическое руководство по настройке безопасности Spring с помощью AccessDecisionVoters

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

1. введение

В большинстве случаев при защите веб-приложения Spring или API REST инструментов, предоставляемых Spring Security, более чем достаточно, но иногда мы ищем более конкретное поведение.

В этом уроке мы напишем пользовательский AccessDecisionVoter и покажем, как его можно использовать для абстрагирования логики авторизации веб-приложения и отделения ее от бизнес-логики приложения.

2. Сценарий

Чтобы продемонстрировать, как работает AccessDecisionVoter , мы реализуем сценарий с двумя типами пользователей, USER и ADMIN, в котором ПОЛЬЗОВАТЕЛЬ может получить доступ к системе только в четные минуты, в то время как ADMIN всегда будет предоставлен доступ.

3. Реализации AccessDecisionVoter

Во-первых, мы опишем несколько реализаций, предоставленных Spring, которые будут участвовать вместе с нашим пользовательским избирателем в принятии окончательного решения об авторизации. Затем мы рассмотрим, как реализовать пользовательский избиратель.

3.1. Реализации AccessDecisionVoter по умолчанию

Spring Security предоставляет несколько реализаций AccessDecisionVoter . Мы будем использовать некоторые из них как часть нашего решения по обеспечению безопасности здесь.

Давайте посмотрим, как и когда голосуют эти реализации избирателей по умолчанию.

AuthenticatedVoter проголосует на основе уровня аутентификации объекта Authentication – в частности, ищет либо полностью аутентифицированного участника, либо аутентифицированного с помощью remember-me, либо, наконец, анонимного.

RoleVoter голосует, если какой-либо из атрибутов конфигурации начинается со строки “ROLE_”. Если это так, он будет искать роль в списке GrantedAuthority объекта Authentication|/.

WebExpressionVoter позволяет нам использовать SpEL (язык выражений Spring) для авторизации запросов с помощью аннотации @PreAuthorize .

Например, если мы используем Java config:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    ...
    .antMatchers("/").hasAnyAuthority("ROLE_USER")
    ...
}

Или с помощью конфигурации XML – мы можем использовать SpEL внутри тега intercept-url , в теге http :


    
    ...

3.2. Реализация пользовательского AccessDecisionVoter

Теперь давайте создадим пользовательский избиратель – реализовав интерфейс AccessDecisionVoter :

public class MinuteBasedVoter implements AccessDecisionVoter {
   ...
}

Первый из трех методов, которые мы должны предоставить, – это метод vote . Метод vote является наиболее важной частью пользовательского избирателя и является местом, где проходит наша логика авторизации.

Метод vote может возвращать три возможных значения:

  • ACCESS_GRANTED – избиратель дает утвердительный ответ
  • ACCESS_DENIED – избиратель дает отрицательный ответ
  • ACCESS_ABSTAIN – избиратель воздерживается от голосования

Давайте теперь реализуем метод vote :

@Override
public int vote(
  Authentication authentication, Object object, Collection collection) {
    return authentication.getAuthorities().stream()
      .map(GrantedAuthority::getAuthority)
      .filter(r -> "ROLE_USER".equals(r) 
        && LocalDateTime.now().getMinute() % 2 != 0)
      .findAny()
      .map(s -> ACCESS_DENIED)
      .orElseGet(() -> ACCESS_ABSTAIN);
}

В нашем методе vote мы проверяем, поступает ли запрос от ПОЛЬЗОВАТЕЛЯ . Если это так, мы возвращаем ACCESS_GRANTED если это четная минута, в противном случае мы возвращаем ACCESS_DENIED. Если запрос не исходит от ПОЛЬЗОВАТЕЛЯ, мы воздерживаемся от голосования и возвращаем ACCESS_ABSTAIN .

Второй метод возвращает, поддерживает ли избиратель определенный атрибут конфигурации. В нашем примере избирателю не нужен какой-либо пользовательский атрибут конфигурации, поэтому мы возвращаем true :

@Override
public boolean supports(ConfigAttribute attribute) {
    return true;
}

Третий метод возвращает, может ли избиратель голосовать за тип защищенного объекта или нет. Поскольку наш избиратель не связан с типом защищенного объекта, мы возвращаем true :

@Override
public boolean supports(Class clazz) {
    return true;
}

4. AccessDecisionManager

Окончательное решение об авторизации обрабатывается AccessDecisionManager .

AbstractAccessDecisionManager содержит список AccessDecisionVoter s, которые отвечают за голосование независимо друг от друга.

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

  • AffirmativeBased – предоставляет доступ, если кто-либо из AccessDecisionVoter s вернет утвердительный голос
  • ConsensusBased – предоставляет доступ, если голосов ” за “больше, чем “против” (игнорируя пользователей, которые воздерживаются)
  • Единогласно – предоставляет доступ, если каждый избиратель либо воздерживается, либо голосует “за”.

Конечно, вы можете реализовать свой собственный AccessDecisionManager с вашей пользовательской логикой принятия решений.

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

В этой части урока мы рассмотрим методы на основе Java и XML для настройки нашего пользовательского AccessDecisionVoter с помощью AccessDecisionManager .

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

Давайте создадим класс конфигурации для Spring Web Security:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}

И давайте определим AccessDecisionManager bean, который использует Единогласный менеджер с нашим настроенным списком избирателей:

@Bean
public AccessDecisionManager accessDecisionManager() {
    List> decisionVoters 
      = Arrays.asList(
        new WebExpressionVoter(),
        new RoleVoter(),
        new AuthenticatedVoter(),
        new MinuteBasedVoter());
    return new UnanimousBased(decisionVoters);
}

Наконец, давайте настроим Spring Security на использование ранее определенного компонента по умолчанию AccessDecisionManager :

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    ...
    .anyRequest()
    .authenticated()
    .accessDecisionManager(accessDecisionManager());
}

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

Если вы используете конфигурацию XML, вам нужно будет изменить свой spring-security.xml файл (или любой файл, содержащий ваши настройки безопасности).

Во-первых, вам нужно будет изменить тег :


  
  ...

Затем добавьте компонент для пользовательского избирателя:


Затем добавьте компонент для AccessDecisionManager :


    
        
            
            
            
            
        
    

Вот пример тега, поддерживающего наш сценарий:


    
        
            
            
        
    

Если вы используете комбинацию конфигурации Java и XML, вы можете импортировать XML в класс конфигурации:

@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class XmlSecurityConfig {
    public XmlSecurityConfig() {
        super();
    }
}

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

В этом уроке мы рассмотрели способ настройки безопасности для веб-приложения Spring с помощью AccessDecisionVoter s. Мы видели, что некоторые избиратели, обеспеченные весенней безопасностью, внесли свой вклад в наше решение. Затем мы обсудили, как реализовать пользовательский AccessDecisionVoter .

Затем мы обсудили, как AccessDecisionManager принимает окончательное решение об авторизации, и мы показали, как использовать реализации, предоставленные Spring, чтобы принять это решение после того, как все избиратели отдали свои голоса.

Затем мы настроили список AccessDecisionVoters с помощью AccessDecisionManager через Java и XML.

Реализацию можно найти в проекте Github .

Когда проект выполняется локально, страница входа в систему доступна по адресу:

http://localhost:8082/login

Учетные данные для ПОЛЬЗОВАТЕЛЯ – “пользователь” и “пропуск”, а учетные данные для АДМИНИСТРАТОРА – “администратор” и “пропуск”.