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
есть роль автора
, и это дает ему разрешения на статьи: создание
и статьи: редактирование :
[users]
zoe.author = password3, author
#Other users...
[roles]
author = articles:create, articles:edit
#Other roles...
Точно так же другие типы областей (такие как встроенная область JDBC) могут быть настроены для связывания разрешений с ролями.
4. Подстановочные разрешения
Реализация разрешений по умолчанию в Широ — это разрешения с подстановочными знаками, гибкое представление для различных схем разрешений.
Мы представляем подстановочные разрешения в Широ строками. Строка разрешения состоит из одного или нескольких компонентов, разделенных двоеточием, например:
articles:edit:1
Значение каждой части строки зависит от приложения, поскольку Широ не применяет никаких правил. Однако в приведенном выше примере мы можем довольно четко интерпретировать строку как иерархию:
- Класс ресурсов, которые мы раскрываем (статьи)
- Действие на таком ресурсе (править)
- Идентификатор конкретного ресурса, на котором мы хотим разрешить или запретить действие
Эта трехуровневая структура ресурс:действие:идентификатор является распространенным шаблоном в приложениях Shiro, поскольку она проста и эффективна для представления множества различных сценариев.
Итак, мы могли бы вернуться к нашему предыдущему примеру, чтобы следовать этой схеме:
Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:edit:123")) {
//Edit article with id 123
}
Обратите внимание, что число компонентов в строке разрешений с подстановочными знаками не обязательно должно быть равно трем, даже если три компонента являются обычным случаем.
4.1. Последствия разрешений и детализация на уровне экземпляра
Разрешения с подстановочными знаками сияют, когда мы объединяем их с другой особенностью разрешений Широ — импликацией.
Когда мы проверяем роли, мы проверяем точное членство: либо у Субъекта
есть определенная роль, либо нет. Другими словами, Широ проверяет роли на равенство.
С другой стороны, когда мы проверяем разрешения, мы проверяем последствия: подразумевают ли разрешения субъекта
то, против которого мы его проверяем?
То, что подразумевается, конкретно зависит от реализации разрешения. Фактически, для разрешений с подстановочными знаками подразумевается частичное совпадение строки с возможностью использования подстановочных компонентов, как следует из названия.
Итак, допустим, мы назначаем роли автора следующие разрешения:
[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. Реализации пользовательских разрешений
Кратко коснемся настройки разрешений. Несмотря на то, что разрешения с подстановочными знаками охватывают широкий спектр сценариев, мы можем захотеть заменить их решением, специально разработанным для нашего приложения.
Предположим, что нам нужно смоделировать разрешения на пути так, чтобы разрешение на пути подразумевало
разрешения на все подпути. На самом деле мы могли бы прекрасно использовать разрешения с подстановочными знаками для этой задачи, но давайте проигнорируем это.
Итак, что нам нужно?
- Реализация
разрешения
_ - сказать Широ об этом
Давайте посмотрим, как достичь обоих пунктов.
5.1. Написание реализации разрешения
Реализация Permission
— это класс с одним методом — подразумевает
:
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. Рассказываем Широ о нашей реализации
Кроме того, существуют различные способы интеграции реализации Permission
в Shiro, но самый простой способ — внедрить собственный 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 отвечает `` за преобразование строкового представления наших разрешений в фактические объекты
Permission :
``
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
Затем мы сможем проверить разрешения на пути:
if(currentUser.isPermitted("/articles/drafts/new-article")) {
log.info("You can access articles");
}
Обратите внимание, что здесь мы настраиваем простую область программно. В типичном приложении мы будем использовать файл shiro.ini
или другие средства, такие как Spring, для настройки Широ и области. Реальный файл shiro.ini
может содержать:
[main]
permissionResolver = com.foreach.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 .