Еще в феврале я писал о своих предстоящих конференциях :
Помимо Вегаса и Польши, в ближайшие несколько месяцев я мог бы выступить на нескольких других мероприятиях: Юта, группа пользователей Java (возможно, в апреле), Jazoon и ÜberConf (если мои предложения будут приняты). Для этих событий я надеюсь представить следующий доклад:
Безопасность веб-приложений: разработка. Проникнуть. Защита. Расслабьтесь.
На этом занятии вы узнаете, как реализовать аутентификацию в ваших веб-приложениях на Java, используя Spring Security, Apache Shiro и хорошую управляемую аутентификацию контейнера Java EE. Вы также узнаете, как защитить свой REST API с помощью OAuth и заблокировать его с помощью SSL.
Изучив, как разработать аутентификацию, я познакомлю вас с OWASP, OWASP Top 10, Руководством по тестированию и Руководством по проверке кода. Далее я расскажу об использовании WebGoat для проверки безопасности вашего приложения и коммерческих инструментов, таких как брандмауэры и ускорители веб-приложений.
Перенесемся через пару месяцев, и я рад сообщить, что я закончил свой доклад в ЮГ-Кувшине, и он был принят в Jazoon и Uber Conf. Для этого выступления я создал презентацию, которая в основном состоит из демонстраций, реализующих базовую аутентификацию, проверку подлинности на основе форм и Ajax с использованием Java EE 6 , Spring Security и Apache Shiro . В процессе создания демонстраций я узнал (или переучился сам), как сделать несколько вещей во всех трех средах:
- Реализовать базовую аутентификацию
- Реализовать аутентификацию на основе форм
- Внедрить Ajax HTTP -> HTTPS-аутентификацию (с программными API)
- Принудительно использовать SSL для определенных URL
- Реализовать файловое хранилище пользователей и паролей (в автономных версиях Jetty / Maven и Tomcat)
- Реализовать базу данных пользователей и паролей (в автономных версиях Jetty / Maven и Tomcat)
- Зашифровать пароли
- Безопасные методы с аннотациями
Для демонстраций я показал аудитории, как делать почти все это, но пропустил автономную работу Tomcat и методы защиты в интересах времени. В июле, когда я сделаю это выступление на ÜberConf, я планирую добавить 1) взлом приложения (чтобы показать дыры в безопасности) и 2) исправление его, чтобы защитить его от уязвимостей.
Я сказал аудитории в UJUG, что я опубликую презентацию и планирую записать скриншоты различных демонстраций, чтобы иметь больше смысла в онлайн-версии презентации. Сегодня я закончил первую заставку, показывающую, как реализовать безопасность с помощью Java EE 6. Ниже представлена презентация (со встроенной заставкой на слайде 10), а также пошаговое руководство.
Учебник по входу в Java EE 6
- Загрузите и запустите приложение
- Реализовать базовую аутентификацию
- Реализовать аутентификацию на основе форм
- Принудительно SSL
- Хранить пользователей в базе данных
- Резюме
Загрузите и запустите приложение.
Для начала загрузите приложение, в котором вы будете внедрять систему безопасности. Это приложение является урезанной версией приложения Ajax Login, которое я написал для своей статьи о реализации Ajax-аутентификации с использованием jQuery, Spring Security и HTTPS . Для запуска приложения вам потребуется Java 6 и Maven. Запустите его, используя mvn jetty: run и откройте http: // localhost: 8080 в вашем браузере. Вы увидите, что это простое приложение CRUD для пользователей, и для добавления или удаления пользователей не требуется вход в систему.
Реализация базовой аутентификации
Первым шагом является защита экрана списка, чтобы людям приходилось входить в систему для просмотра пользователей. Для этого добавьте следующее в конец файла src / main / webapp / WEB-INF / web.xml :
<security-constraint>
<web-resource-collection>
<web-resource-name>users</web-resource-name>
<url-pattern>/users</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ROLE_ADMIN</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Java EE Login</realm-name>
</login-config>
<security-role>
<role-name>ROLE_ADMIN</role-name>
</security-role>
В этот момент, если вы перезапустите Jetty (Ctrl + C и jetty: запустите снова), вы получите ошибку об отсутствующем LoginService. Это происходит потому, что Jetty не знает, где находится область «Java EE Login». Добавьте следующее в pom.xml сразу после </ webAppConfig> в конфигурации плагина Jetty.
Файл realm.properties уже существует в проекте и содержит имена пользователей и пароли. Запустите приложение снова, используя mvn jetty: run, и вам будет предложено войти в систему, когда вы нажмете на вкладку «Пользователи». Введите admin / admin для входа.
После входа в систему вы можете попытаться выйти, нажав ссылку «Выйти» в правом верхнем углу. Это вызывает LogoutController со следующим кодом, который выводит пользователя из системы.
public void logout(HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate();
response.sendRedirect(request.getContextPath());
}
Вы заметите, что нажатие на эту ссылку не приведет к выходу из системы, даже если сеанс признан недействительным. Единственный способ выйти из системы с базовой аутентификацией — закрыть браузер. Чтобы получить возможность выхода из системы, а также получить больший контроль над внешним видом входа в систему, вы можете реализовать аутентификацию на основе форм.
Реализация проверки подлинности на основе форм
Чтобы перейти от базовой проверки подлинности на основе форм, просто замените <login-confi> в файле web.xml следующим:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login.jsp?error=true</form-error-page>
</form-login-config>
</login-config>
Страница login.jsp уже существует в проекте, в каталоге src / main / webapp . Этот JSP имеет 3 важных элемента: 1) форму, которая представляет «$ {contextPath} / j_security_check», 2) элемент ввода с именем «j_username» и 3) элемент ввода с именем «j_password». Если вы перезапустите Jetty, вам будет предложено войти в систему с помощью этого JSP вместо диалогового окна базовой аутентификации.
Принудительный SSL
Еще одна вещь, которую вы можете реализовать для защиты своего приложения, — это принудительное использование SSL для определенных URL-адресов. Чтобы сделать это с тем же <security-constraint>, который у вас уже есть в web.xml, добавьте следующее после </ auth-constraint>:
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
Чтобы настроить Jetty для прослушивания порта SSL, добавьте следующее сразу после </ loginServices> в свой файл pom.xml:
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<forwarded>true</forwarded>
<port>8080</port>
</connector>
<connector implementation="org.eclipse.jetty.server.ssl.SslSelectChannelConnector">
<forwarded>true</forwarded>
<port>8443</port>
<maxIdleTime>60000</maxIdleTime>
<keystore>${project.build.directory}/ssl.keystore</keystore>
<password>appfuse</password>
<keyPassword>appfuse</keyPassword>
</connector>
</connectors>
Хранилище ключей должно быть сгенерировано для успешного запуска Jetty, поэтому добавьте keytool-maven-plugin чуть выше jetty-maven-plugin в pom.xml.
Теперь, если вы перезапустите Jetty, перейдите на http: // localhost: 8080 и нажмите на вкладку «Пользователи», вы получите 403. Какого черта ?! Когда это впервые случилось со мной, мне потребовалось некоторое время, чтобы понять. Оказывается, что Jetty не перенаправляет на HTTPS при использовании аутентификации Java EE, поэтому вы должны вручную ввести https: // localhost: 8443 / (или добавить фильтр для перенаправления для вас). Если вы развернули это же приложение на Tomcat (после включения SSL ), оно будет перенаправлено для вас.
Хранение пользователей в базе данных
Наконец, чтобы хранить пользователей в базе данных вместо файла, вам необходимо изменить <loginService> в конфигурации плагина Jetty. Замените существующий элемент <loginService> следующим:
<loginServices>
<loginService implementation="org.eclipse.jetty.security.JDBCLoginService">
<name>Java EE Login</name>
<config>${basedir}/src/test/resources/jdbc-realm.properties</config>
</loginService>
</loginServices>
Файл jdbc-realm.properties уже существует в проекте и содержит параметры базы данных и имена таблиц / столбцов для пользователя и информацию о роли.
jdbcdriver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost/appfuse username = root password = usertable = app_user usertablekey = id usertableuserfield = username usertablepasswordfield = password roletable = role roletablekey = id roletablerolefield = name userroletable = user_role userroletableuserkey = user_id userroletablerolekey = role_id cachetime = 300
Конечно, вам нужно установить MySQL, чтобы это работало. После установки вы сможете создать базу данных appfuse и заполнить ее с помощью следующих команд:
mysql -u root -p -e 'create database appfuse' curl https://gist.github.com/raw/958091/ceecb4a6ae31c31429d5639d0d1e6bfd93e2ea42/create-appfuse.sql > create-appfuse.sql mysql -u root -p appfuse < create-appfuse.sql
Затем вам нужно настроить Jetty таким образом, чтобы в его classpath был установлен драйвер JDBC MySQL. Для этого добавьте следующую зависимость сразу после элемента <configuration> (перед <executetions>) в pom.xml:
<dependencies>
<!-- MySQL for JDBC Realm -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.14</version>
</dependency>
</dependencies>
Теперь запустите файл jetty-password.sh в корневом каталоге проекта, чтобы сгенерировать пароль по вашему выбору. Например:
$ sh jetty-password.sh javaeelogin javaeelogin OBF:1vuj1t2v1wum1u9d1ugo1t331uh21ua51wts1t3b1vur MD5:53b176e6ce1b5183bc970ef1ebaffd44
Последние две строки обфусцированы и MD5 версии пароля. Обновите пароль администратора для этого нового значения. Вы можете сделать это с помощью следующего оператора SQL.
UPDATE app_user SET password='MD5:53b176e6ce1b5183bc970ef1ebaffd44' WHERE username = 'admin';
Теперь, если вы перезапустите Jetty, вы сможете войти с помощью admin / javaeelogin и просмотреть список пользователей.
Резюме
В этом руководстве вы узнали, как реализовать аутентификацию с использованием стандартной Java EE 6. В дополнение к базовой конфигурации XML в HttpServletRequest для Java EE 6 и Servlet 3.0 также есть несколько новых методов:
- проверку подлинности (ответ)
- логин (пользователь, пароль)
- выйти()
В этом руководстве не показано, как их использовать, но я немного поиграл с ними, как часть моей демонстрации UJUG при реализации аутентификации Ajax. Я обнаружил, что login () работал, но не сохранил аутентификацию для сеанса пользователей. Я также обнаружил, что после вызова logout () мне все еще нужно было аннулировать сеанс, чтобы полностью выйти из системы пользователя. Есть некоторые дополнительные ограничения, которые я нашел с аутентификацией Java EE, а именно:
- Нет сообщений об ошибках при неудачных входах
- Не помню меня
- Нет автоматического перенаправления с HTTP на HTTPS
- Контейнер должен быть настроен
- Не поддерживает регулярные выражения для URL
Конечно, нет сообщений об ошибках, указывающих, почему вход в систему не удался, вероятно, хорошая вещь (вы не хотите сообщать пользователям, почему их учетные данные не были выполнены). Однако, когда вы пытаетесь выяснить, правильно ли сконфигурирован ваш контейнер, отсутствие регистрации в контейнере может быть проблемой.
В ближайшие пару недель я опубликую часть II этой серии, где я покажу вам, как реализовать этот же набор функций с помощью Spring Security. А пока, пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы.
От http://raibledesigns.com/rd/entry/java_web_application_security_part