«1. Введение

В этом руководстве мы рассмотрим, как реализовать детальное управление доступом на основе разрешений с помощью среды безопасности Apache Shiro Java.

2. Настройка

Мы будем использовать ту же настройку, что и при знакомстве с Shiro, то есть мы добавим только модуль shiro-core в наши зависимости:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
</dependency>

Кроме того, для В целях тестирования мы будем использовать простую INI-область, поместив следующий файл shiro.ini в корень пути к классам:

[users]
jane.admin = password, admin
john.editor = password2, editor
zoe.author = password3, author
 
[roles]
admin = *
editor = articles:*
author = articles:create, articles:edit

Затем мы инициализируем Широ с указанной выше областью:

IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);
SecurityUtils.setSecurityManager(securityManager);

3. Роли и разрешения

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

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

Набор ролей обычно разрабатывается заранее и редко меняется в соответствии с новыми бизнес-требованиями. Однако роли также могут быть определены динамически — например, администратором.

С Широ у нас есть несколько способов проверить, есть ли у пользователя определенная роль. Самый простой способ — использовать метод hasRole:

Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")) {       
    logger.info("Welcome Admin");              
}

3.1. Разрешения

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

Мы можем сделать лучше; вот почему мы сейчас введем понятие разрешений. Разрешения представляют собой то, что может делать программное обеспечение, что мы можем разрешить или запретить, а не то, кто может это делать. Например, «отредактируйте профиль текущего пользователя», «одобрите документ» или «создайте новую статью».

Широ делает очень мало предположений о разрешениях. В простейшем случае разрешения представляют собой простые строки:

Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
    //Create a new article
}

Обратите внимание, что использование разрешений в Широ совершенно необязательно.

3.2. Связывание разрешений с пользователями

Широ имеет гибкую модель связывания разрешений с ролями или отдельными пользователями. Однако обычные области, включая простую область INI, которую мы используем в этом руководстве, связывают разрешения только с ролями.

Итак, пользователь, идентифицированный Принципалом, имеет несколько ролей, и каждая роль имеет несколько Разрешений.

Например, мы можем видеть, что в нашем INI-файле пользователь zoe.author имеет роль автора, и это дает ему разрешения article:create и article:edit:

[users]
zoe.author = password3, author
#Other users...

[roles]
author = articles:create, articles:edit
#Other roles...

Аналогично, другие типы областей ( например встроенная область JDBC) можно настроить для связывания разрешений с ролями.

4. Разрешения с подстановочными знаками

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

Мы представляем права доступа в Широ строками. Строка разрешения состоит из одного или нескольких компонентов, разделенных двоеточием, например:

articles:edit:1

Значение каждой части строки зависит от приложения, поскольку Широ не применяет никаких правил. Однако в приведенном выше примере мы можем довольно четко интерпретировать строку как иерархию:

  1. The class of resources we’re exposing (articles)
  2. An action on such a resource (edit)
  3. The id of a specific resource on which we want to allow or deny the action

Эта трехуровневая структура ресурса:действие:идентификатор является распространенным шаблоном в приложениях Широ, поскольку она одновременно проста и эффективна для представления множества различных действий. сценарии.

Итак, мы могли бы вернуться к нашему предыдущему примеру, чтобы следовать этой схеме:

Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:edit:123")) {
    //Edit article with id 123
}

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

4.1. Импликация разрешений и детализация на уровне экземпляра

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

Когда мы проверяем роли, мы проверяем точное членство: либо у Субъекта есть определенная роль, либо нет. Другими словами, Широ проверяет роли на равенство.

«С другой стороны, когда мы проверяем разрешения, мы проверяем последствия: подразумевают ли разрешения Субъекта то, против которого мы его проверяем?

То, что подразумевается конкретно, зависит от реализации разрешения. Фактически, для разрешений с подстановочными знаками подразумевается частичное совпадение строки с возможностью использования подстановочных компонентов, как следует из названия.

Итак, допустим, мы назначаем роли автора следующие разрешения:

[roles]
author = articles:*

Тогда всем с ролью автора будут разрешены все возможные операции над статьями:

Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
    //Create a new article
}

То есть строка article:* будет соответствовать любому подстановочному разрешению, первым компонентом которого являются статьи.

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

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

if (subject.isPermitted("articles:edit:1")) { //Better than "articles:*"
    //Edit article
}

5. Реализации пользовательских разрешений

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

Предположим, что нам нужно смоделировать разрешения на пути так, чтобы разрешение на пути подразумевало разрешения на все подпути. На самом деле мы могли бы прекрасно использовать разрешения с подстановочными знаками для этой задачи, но давайте проигнорируем это.

Итак, что нам нужно?

  1. a Permission implementation
  2. to tell Shiro about it

Давайте посмотрим, как добиться обеих точек.

5.1. Написание реализации разрешения

Реализация разрешения — это класс с одним методом — подразумевает:

public class PathPermission implements Permission {

    private final Path path;

    public PathPermission(Path path) {
        this.path = path;
    }

    @Override
    public boolean implies(Permission p) {
        if(p instanceof PathPermission) {
            return ((PathPermission) p).path.startsWith(path);
        }
        return false;
    }
}

Метод возвращает true, если это подразумевает другой объект разрешения, и возвращает false в противном случае.

5.2. Рассказываем Широ о нашей реализации

Далее, существуют различные способы интеграции реализации разрешения в Широ, но самый простой способ — внедрить собственный PermissionResolver в нашу область:

IniRealm realm = new IniRealm();
Ini ini = Ini.fromResourcePath(Main.class.getResource("/com/.../shiro.ini").getPath());
realm.setIni(ini);
realm.setPermissionResolver(new PathPermissionResolver());
realm.init();

SecurityManager securityManager = new DefaultSecurityManager(realm);

PermissionResolver отвечает за преобразование строковое представление наших разрешений на фактические объекты разрешений:

public class PathPermissionResolver implements PermissionResolver {
    @Override
    public Permission resolvePermission(String permissionString) {
        return new PathPermission(Paths.get(permissionString));
    }
}

Нам придется изменить наш предыдущий shiro.ini с разрешениями на основе пути:

[roles]
admin = /
editor = /articles
author = /articles/drafts

Затем мы сможем проверить разрешения on paths:

if(currentUser.isPermitted("/articles/drafts/new-article")) {
    log.info("You can access articles");
}

Обратите внимание, что здесь мы программно настраиваем простую область. В типичном приложении мы будем использовать файл shiro.ini или другие средства, такие как Spring, для настройки Широ и области. Реальный файл shiro.ini может содержать:

[main]
permissionResolver = com.baeldung.shiro.permissions.custom.PathPermissionResolver
dataSource = org.apache.shiro.jndi.JndiObjectFactory
dataSource.resourceName = java://app/jdbc/myDataSource

jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $dataSource 
jdbcRealm.permissionResolver = $permissionResolver

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

В этой статье мы рассмотрели, как Apache Shiro реализует управление доступом на основе разрешений.

Как всегда, реализации всех этих примеров и фрагментов кода доступны на GitHub.