Статьи

Apache Shiro Part 1 — Основы

Apache Shiro , первоначально называемый JSecurity, является средой безопасности Java. Он был принят и стал проектом высшего уровня Apache в 2010 году. Он стремится быть мощным и простым в использовании.

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

Основные функции Shiro:

  • аутентификация,
  • авторизации,
  • криптография,
  • управление сессиями.

В этой статье мы попытаемся продемонстрировать различные функции Shiro. Мы начинаем с простого незащищенного веб-приложения, затем добавляем в него функции безопасности. Весь код доступен в проекте SimpleShiroSecuredApplication на Github.
Необеспеченное приложение

Незащищенный код приложения находится в ветке unsecured_application . Приложение представляет собой внутреннюю систему для вымышленной компании. Компания имеет четыре отдела:

  • администраторы,
  • ремонтники,
  • ученые,
  • продажи.

У каждого отдела есть своя страница. Каждая страница содержит кнопки, которые используются пользователями для выполнения своей работы. Когда пользователь нажимает кнопку, работа завершена. Например, любой ремонтник может перейти на страницу ремонта и нажать кнопку «Ремонт холодильника». Кнопка восстанавливает холодильник и показывает сообщение об успехе.

У каждого пользователя есть своя страница аккаунта. Страница аккаунта содержит личные данные пользователя. Поскольку у незащищенного приложения еще нет пользователей, страница учетной записи ничего не делает. Кроме того, есть страница, которая содержит все функции приложения. На этой странице можно сделать все, что угодно.

Любой может выполнить любую работу и просмотреть все страницы. Пример приложения запускается в тестовом классе RunWaitTest . Не рекомендуется использовать модульный тест таким образом, но сейчас это не важно. Если вы запустите класс, приложение будет доступно по адресу http: // localhost: 9180 / simpleshirosecuredapplication / url.

Добавление аутентификации

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

Для простейших приложений достаточно хранить имена пользователей и пароли в текстовых файлах. В более реалистичном сценарии имя пользователя и пароль хранятся в постоянном хранилище или проверка выполняется с помощью другой системы, такой как ldap или активный каталог. Широ поддерживает все упомянутые методы аутентификации. Если встроенных функций проверки подлинности недостаточно, можно расширить платформу за счет собственной реализации проверки.

В этой главе мы добавляем в приложение аутентификацию на основе имени пользователя и пароля. Имя пользователя и пароль хранятся в статическом текстовом файле Shiro ini.

Новые требования: есть возможность авторизоваться и выйти из системы. Приложение должно быть доступно только зарегистрированным пользователям. Успешный вход перенаправляет пользователя на страницу своего аккаунта. Все функции и страницы приложения по-прежнему доступны любому зарегистрированному пользователю.

Необходимые шаги:

  • добавить Apache Shiro,
  • создать страницу входа в систему,
  • настроить пользователей и пароль,
  • создать страницу выхода.

Добавить Apache Shiro

Shiro интегрируется в веб-приложение через фильтры сервлетов. Фильтр перехватывает запросы и ответы перед сервлетом и выполняет все необходимые задачи (такие как идентификация текущего зарегистрированного пользователя, присоединение зарегистрированного пользователя к текущему потоку,…). Фильтры Shiro по умолчанию обеспечивают основные функции безопасности, такие как:

  • принудительный вход пользователя,
  • соблюдение ССЛ,
  • проверка прав доступа к странице.

Если вы хотите узнать больше о стандартных фильтрах Широ, лучше всего начать с перечисления DefaultFilter . В нем перечислены все фильтры Широ, доступные по умолчанию. Если этого недостаточно для ваших нужд, вы можете создать свой.

Мы будем использовать настраиваемый IniShiroFilter . Он читает конфигурацию Shiro из INI-файла и инициализирует структуру безопасности. Он не выполняет никаких проверок безопасности. Проверка прав доступа, логин пользователя, проверка протокола и т. Д. Делегируются либо по умолчанию, либо по специальным фильтрам. IniShiroFilter только инициализирует их.

Конфигурация Ini описана как в документации, так и в javadoc . Конфигурация файла Ini состоит из четырех разделов:

  • Раздел [основной] содержит инициализацию Широ. Фильтры и пользовательские объекты настраиваются здесь.
  • Раздел [пользователи] определяет пользователей, пароли и роли.
  • Раздел [role] связывает роли с разрешениями.
  • Раздел [urls] определяет права доступа к страницам приложения (urls). Это делается путем привязки либо стандартных, либо пользовательских фильтров к URL-адресам.

Добавьте зависимость Apache Shiro в pom.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<properties>
    <shiro.version>1.1.0</shiro.version>
</properties>
<dependencies>
    <dependency>
        <groupid>org.apache.shiro</groupid>
        <artifactid>shiro-core</artifactid>
        <version>${shiro.version}</version>
    </dependency>
    <dependency>
        <groupid>org.apache.shiro</groupid>
        <artifactid>shiro-web</artifactid>
        <version>${shiro.version}</version>
    </dependency>
</dependencies>

Создайте файл Shiro.ini и поместите его в путь к классам. Сконфигурируйте web.xml для вызова IniShiroFilter перед каждым запросом:

01
02
03
04
05
06
07
08
09
10
11
12
13
<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>classpath:Shiro.ini</param-value>
    </init-param>
</filter>
 
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Создать страницу входа

Страница входа — это простая html-страница с полями для отправки, именем пользователя и паролем. Функции входа в систему обрабатываются по умолчанию Shiro authc filter. Фильтр Authc разрешает доступ к URL только зарегистрированным пользователям. Если пользователь не вошел в систему, фильтр перенаправит его на страницу входа.

Форма на странице входа должна иметь имя loginform, а метод отправки должен быть post. Создайте страницу login.jsp :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<form name="loginform" action="" method="post">
<table align="left" border="0" cellspacing="0" cellpadding="3">
    <tr>
        <td>Username:</td>
        <td><input type="text" name="user" maxlength="30"></td>
    </tr>
    <tr>
        <td>Password:</td>
        <td><input type="password" name="pass" maxlength="30"></td>
    </tr>
    <tr>
        <td colspan="2" align="left"><input type="checkbox" name="remember"><font size="2">Remember Me</font></td>
    </tr>
    <tr>
        <td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td>
    </tr>
</table>
</form>

Включите фильтр authc для всех страниц приложения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
[main]
# specify login page
authc.loginUrl = /simpleshirosecuredapplication/account/login.jsp
 
# name of request parameter with username; if not present filter assumes 'username'
authc.usernameParam = user
# name of request parameter with password; if not present filter assumes 'password'
authc.passwordParam = pass
# does the user wish to be remembered?; if not present filter assumes 'rememberMe'
authc.rememberMeParam = remember
 
# redirect after successful login
authc.successUrl  = /simpleshirosecuredapplication/account/personalaccountpage.jsp
 
[urls]
# enable authc filter for all application pages
/simpleshirosecuredapplication/**=authc

Обновление: Shiro автоматически выполняет контекстно-зависимое сопоставление путей. Поскольку SimpleShiroSecuredApplication не имеет установленного пути к контексту, необходимы полные пути в Shiro.ini. Однако, если путь к контексту приложения будет / simpleshirosecuredapplication, тогда пути могут быть относительными: например, simple / ** = authc или /account/personalaccountpage.jsp.

Так как отправлять незашифрованные имя пользователя и пароль через сеть небезопасно, мы должны принудительно войти в систему через ssl. Фильтр Ssl делает именно это. Он имеет необязательный параметр: номер порта ssl. Если параметр порта опущен, он использует ssl-порт по умолчанию 443.

Перед настройкой ssl в Shiro, мы должны включить его на веб-сервере. Как это сделать, зависит от веб-сервера. Мы покажем, как включить его в Jetty. Сначала создайте хранилище ключей с самозаверяющим сертификатом:

1
keytool -genkey -keyalg RSA -alias jetty -keystore keystore -storepass secret -validity 360 -keysize 2048

Ответьте на все вопросы и в конце нажмите Enter, чтобы пароль хранилища ключей и пароли ключей совпадали.

Во-вторых, добавьте хранилище ключей в проект и настройте Jetty для использования ssl. Java-код доступен в классе AbstractContainerTest .

Теперь можно настроить ssl-фильтр в Shiro.ini:

1
2
3
4
5
[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc
# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc

Настроить пользователей и пароль

SimpleShiroSecuredApplication теперь доступно только для зарегистрированных пользователей. Теперь нам нужно добавить несколько пользователей, чтобы люди могли войти в систему. Настройка выполняется в разделе [users] файла Shiro.ini. Формат записей раздела:

1
username = password, roleName1, roleName2, ..., roleNameN

Следующий раздел создает семь пользователей, у всех один и тот же пароль «heslo»:

1
2
3
4
5
6
7
8
[users]
administrator=heslo,Administrator
friendlyrepairmen=heslo,repairmen
unfriendlyrepairmen=heslo,repairmen
mathematician=heslo,scientist
physicien=heslo,scientist
productsales=heslo,sales
servicessales=heslo,sales

Теперь можно войти в приложение. Однако разумное сообщение об ошибке не отображается, если пользователь допустил ошибку. Кроме того, пароли хранятся в текстовом файле.

Обработка ошибок

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

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

Каждый раз, когда возникает ошибка аутентификации, выдается исключение. По умолчанию фильтр проверки подлинности формы перехватывает исключение и сохраняет его имя класса в параметре запроса. Поскольку мы хотим настроить отправку данных на страницу, мы должны расширить FormAuthenticationFilter и переопределить метод setFailureAttribute :

1
2
3
4
5
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
  String message = ae.getMessage();
  request.setAttribute(getFailureKeyAttribute(), message);
}

Замените фильтр авторизации формы VerboseFormAuthenticationFilter и настройте его для использования атрибута запроса ‘simpleShiroApplicationLoginFailure’ для хранения информации об ошибке:

1
2
3
4
5
[main]
# replace form authentication filter with verbose filter
authc = org.meri.simpleshirosecuredapplication.servlet.VerboseFormAuthenticationFilter
# request parameter with login error information; if not present filter assumes 'shiroLoginFailure'
authc.failureKeyAttribute=simpleShiroApplicationLoginFailure

Показать ошибку на странице login.jsp:

1
2
3
4
5
6
7
8
<%
  String errorDescription = (String) request.getAttribute("simpleShiroApplicationLoginFailure");
  if (errorDescription!=null) {
%>
Login attempt was unsuccessful: <%=errorDescription%>
<%
  }
%>

Осторожно: реальное приложение не должно показывать слишком много информации об ошибках входа. Сообщение «Попытка входа не удалась». без дополнительной информации обычно достаточно.

Хеширование паролей

В текущей версии приложения все пароли хранятся в виде обычного текста. Лучше хранить и сравнивать только хэши паролей.

Ответственные за аутентификацию объекты называются сферами . По умолчанию Shiro использует IniRealm с подключаемым сопоставителем паролей для сравнения паролей. Мы заменим пароли в ini на их хеш-коды SHA-256 и настроим IniRealm для использования хеш-сопоставления SHA-256.

Сгенерируйте SHA-256 хэш пароля:

1
2
3
4
5
6
import org.apache.shiro.crypto.hash.Sha256Hash;
 
public static void main(String[] args) {
    Sha256Hash sha256Hash = new Sha256Hash("heslo");
    System.out.println(sha256Hash.toHex());
}

Настройте Широ для сравнения хэшей паролей вместо самого пароля:

1
2
3
4
5
6
7
[main]
# define matcher matching hashes instead of passwords
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256
 
# enable matcher in iniRealm (object responsible for authentication)
iniRealm.credentialsMatcher = $sha256Matcher

Замените пароли пользователей хешами паролей:

1
2
3
4
5
6
7
8
[users]
administrator=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, Administrator
friendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
unfriendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
mathematician=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, scientist
physicien=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,  scientist
productsales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,        sales
servicessales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,  sales

Примечание: указывать соль в конфигурации ini невозможно.

Создать страницу выхода

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

1
2
//acquire currently logged user and log him out
SecurityUtils.getSubject().logout();

Страница выхода из системы выглядит следующим образом:

1
2
3
<%@ page import="org.apache.shiro.SecurityUtils" %>
<% SecurityUtils.getSubject().logout();%>
You have succesfully logged out.

Добавление авторизации

Завершаем первую часть добавлением авторизации в приложение. Мы начинаем с ограничения доступа пользователей к страницам. Ни один пользователь не должен видеть страницы других отделов. Это обеспечивает лишь частичную безопасность проекта, поскольку пользователь по-прежнему может использовать страницу «все функции приложения» или редактировать URL-адрес в браузере для выполнения каких-либо действий. Мы будем называть это авторизацией на уровне страниц.

Затем мы ограничиваем способность пользователей выполнять действия самостоятельно. Даже если пользователь откроет страницу «все функции приложения» или отредактирует URL-адрес в браузере, ему будет разрешено выполнять только функции, характерные для его отдела. Мы будем называть это авторизацией на уровне функций.

Новые требования: пользователь не может видеть страницы отделов, к которым он не принадлежит. Пользователь может выполнять только свои ведомственные функции. Единственным исключением из предыдущих правил является администратор, который может выполнять как административные, так и ремонтные функции.

Авторизация страницы

Авторизация на уровне страницы осуществляется с помощью фильтра ролей. Параметрическая часть фильтра может содержать любое количество ролей. Зарегистрированный пользователь может получить доступ к странице, только если у него есть все предоставленные роли.

Как обычно, фильтр ролей настраивается в файле Shiro.ini:

01
02
03
04
05
06
07
08
09
10
11
12
[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc
 
# only users with some roles are allowed to use role-specific pages
/simpleshirosecuredapplication/repairmen/**=authc, roles[repairman]
/simpleshirosecuredapplication/sales/**=authc, roles[sales]
/simpleshirosecuredapplication/scientists/**=authc, roles[scientist]
/simpleshirosecuredapplication/adminarea/**=authc, roles[Administrator]
 
# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc

Проверьте работоспособность системы безопасности: войдите в систему как любой пользователь продаж, нажмите «Домой», нажмите ссылку «Страница ремонта». Вы увидите ужасную ошибку.

Мы завершаем авторизацию страницы и заменяем ошибку перенаправлением на страницу ошибки. Фильтры Shiro по умолчанию имеют свойство unauthorizedUrl. В случае несанкционированного доступа фильтр перенаправит пользователя на указанный URL-адрес.

1
2
3
[main]
# redirect to an error page if user does not have access rights
roles.unauthorizedUrl = /simpleshirosecuredapplication/account/accessdenied.jsp

accessdenied.jsp:

1
2
3
<body>
Sorry, you do not have access rights to that area.
</body>

Функции Авторизация

Все ведомственные страницы защищены сейчас. Тем не менее, любой пользователь может выполнять любую функцию на странице «все функции приложения». Более того, любой зарегистрированный пользователь может редактировать URL и, таким образом, выполнять любые действия. Например, если вы войдете в систему как отдел продаж и поместите https: // localhost: 8443 / simpleshirosecuredapplication / masterservlet? Action = MANAGE_REPAIRMEN в URL, приложение также выполнит функцию управления ремонтниками (тогда оно выдаст исключение нулевого указателя, но нарушение безопасности было уже сделано).

Мы присваиваем уникальное разрешение каждой функции. Они разбиты на группы:

  • все разрешения находятся в группе «функции»,
  • все административные разрешения находятся в группе «Управление»,
  • все разрешения на ремонт находятся в группе «ремонт»,
  • все разрешения на продажу находятся в группе «продажа»,
  • все научные разрешения находятся в группе «наука».

Shiro поддерживает многоуровневые разрешения, представленные в виде строк. Уровни разделены символом «:». Например, «функции: управление: ремонтники» имеет три уровня: «функции», «управление» и «ремонтник». Многоуровневые разрешения позволяют легко группировать разрешения. Например, научная группа относится к группе функций и содержит три разрешения:

  • Функции: наука: исследования,
  • Функции: наука: writearticle,
  • Функции: наука: preparetalk.

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

1
2
3
4
5
6
7
8
public String doIt() {
    String neededPermission = getNeededPermission();
    // acquire logged user and check permission
    if (SecurityUtils.getSubject().isPermitted(neededPermission))
        return "Function " + getName() + " run succesfully.";
 
    throw new UnauthorizedException("Logged user does not have " + neededPermission + " permission");
}

ПРИМЕЧАНИЕ. Другой способ достижения этой цели — аннотации.

Сервлет PerformFunctionAndGoBackServlet перехватывает исключение авторизации и преобразует его в сообщение об ошибке:

01
02
03
04
05
06
07
08
09
10
11
private String performAction(String actionName) {
    try {
        Actions action = findAction(actionName);
        String result = action == null ? null : action.doIt();
        log.debug("Performed function with result: " + result);
        return result;
    } catch (ShiroException ex) {
        log.debug("Function failed with " + ex.getMessage() + " message.");
        return "Error: " + ex.getMessage();
    }
}

Наконец, нам нужно настроить разрешения для ролей в файле Shiro.ini. Shiro поддерживает шаблоны для многоуровневых разрешений. Таким образом, нам не нужно указывать каждое ведомственное разрешение отдельно:

1
2
3
4
5
6
7
8
[roles]
# members of departments should be able to perform all departmental functions
sales=functions:sale:*
scientist=functions:science:*
repairman=functions:repair:*
 
# administrators are able to do all management functions and repair functions
Administrator=functions:manage:*,functions:repair:*

Теперь вы можете попробовать функции на странице «все функции приложения». Если зарегистрированный пользователь не имеет необходимых разрешений, в верхней части страницы появляется сообщение об ошибке. Более того, если вы войдете в систему как отдел продаж и попытаетесь взломать https: // localhost: 8443 / simpleshirosecuredapplication / masterservlet? Action = MANAGE_REPAIRMEN, вы увидите сообщение об ошибке в консоли (вместо сообщения об успехе).

Конец

Окончательное приложение доступно в ветке static_authentication_and_authorization на Github.

Во второй части мы создадим пользовательскую область и переместим пользователей, пароли, роли и разрешения из INI-файла в базу данных. Третья часть посвящена пакету криптографии Apache Shiro.

Ссылка: Apache Shiro Part 1 — Основы от нашего партнера JCG Марии Юрковичовой в блоге This Is Stuff .