Основная конфигурация securityconfig
Наша конфигурация будет уметь:
Введение
Хотелось бы в данном обзоре обсудить такую тему, как безопасность веб-приложений.
В Java есть несколько технологий, которые обеспечивают безопасность:
Login form in java swing with source code tutorial | login page
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
publicclassLoginFrameextendsJFrameimplementsActionListener{
Container container=getContentPane();
JLabel userLabel=newJLabel(“USERNAME”);
JLabel passwordLabel=newJLabel(“PASSWORD”);
JTextField userTextField=newJTextField();
JPasswordField passwordField=newJPasswordField();
JButton loginButton=newJButton(“LOGIN”);
JButton resetButton=newJButton(“RESET”);
JCheckBox showPassword=newJCheckBox(“Show Password”);
LoginFrame(){
setLayoutManager();
setLocationAndSize();
addComponentsToContainer();
addActionEvent();
}
publicvoidsetLayoutManager(){
container.setLayout(null);
}
publicvoidsetLocationAndSize(){
userLabel.setBounds(50,150,100,30);
passwordLabel.setBounds(50,220,100,30);
userTextField.setBounds(150,150,150,30);
passwordField.setBounds(150,220,150,30);
showPassword.setBounds(150,250,150,30);
loginButton.setBounds(50,300,100,30);
resetButton.setBounds(200,300,100,30);
}
publicvoidaddComponentsToContainer(){
container.add(userLabel);
container.add(passwordLabel);
container.add(userTextField);
container.add(passwordField);
container.add(showPassword);
container.add(loginButton);
container.add(resetButton);
}
publicvoidaddActionEvent(){
loginButton.addActionListener(this);
resetButton.addActionListener(this);
showPassword.addActionListener(this);
}
@Override
publicvoidactionPerformed(ActionEvente){
//Coding Part of LOGIN button
if(e.getSource()==loginButton){
StringuserText;
StringpwdText;
userText=userTextField.getText();
pwdText=passwordField.getText();
if(userText.equalsIgnoreCase(“mehtab”)&&pwdText.equalsIgnoreCase(“12345”)){
JOptionPane.showMessageDialog(this,“Login Successful”);
}else{
JOptionPane.showMessageDialog(this,“Invalid Username or Password”);
}
}
//Coding Part of RESET button
if(e.getSource()==resetButton){
userTextField.setText(“”);
passwordField.setText(“”);
}
//Coding Part of showPassword JCheckBox
if(e.getSource()==showPassword){
if(showPassword.isSelected()){
passwordField.setEchoChar((char)0);
}else{
passwordField.setEchoChar(‘*’);
}
}
}
}
publicclassLogin{
publicstaticvoidmain(String[]a){
LoginFrame frame=newLoginFrame();
frame.setTitle(“Login Form”);
frame.setVisible(true);
frame.setBounds(10,10,370,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
Аутентификация
Настройки аутентификации зачастую состоят из двух частей: настроек на стороне сервера и настроек на стороне веб-приложения, которое на этом сервере работает. Настройки безопасности веб-приложения не могут не взаимодействовать с настройками безопасности веб-сервера хотя бы по той причине, что веб-приложение не может не взаимодействовать с веб-сервером.
Мы с Вами не зря переключились на Tomcat, т.к. Tomcat имеет хорошо описанную архитектуру (см. “
“). Из описания этой архитектуры видно, что Tomcat как веб-сервер представляет веб-приложение как некоторый контекст, который и называют ”
“. Этот контекст позволяет каждому веб-приложению иметь свои настройки, изолированные от других веб-приложений. Кроме того, веб-приложение может влиять на настройки этого контекста. Гибко и удобно. Для более глубокого понимания рекомендуется к прочтению статья “
” и раздел документации Tomcat ”
“.
Как выше было сказано, наше веб-приложение может влиять на Tomcat Context нашего приложения при помощи файла
/META-INF/context.xml
. И одной из очень важных настроек, на которую мы можем повлиять, является Security Realms.
Security Realms
— это некоторая “область безопасности”. Область, для которой указаны определенные настройки безопасности. Соответственно, используя Security Realm мы применяем настройки безопасности, определённые для этого Realm. Security Realms управляются контейнером, т.е. веб-сервером, а не нашим веб-приложением.
” описывает Realm как набор данных о пользователях и их ролях для выполнения аутентификации. Tomcat предоставляет набор различных реализаций Security Realm’ов, одним из которых является ”
“.
Разобравшись немного с терминологией, давайте опишем Tomcat Context в файле
/META-INF/context.xml
Веб-приложение
Итак, нам понадобится веб-приложение. В его создании нам поможет система автоматической сборки проектов Gradle. Благодаря использования Gradle мы сможем при помощи выполнения небольших команд собирать Java проект в нужном нам формате, создавать автоматически нужную структуру каталогов и многое другое.
Подробнее про Gradle можно прочитать в кратком обзоре: “
” или в официальной документации ”
“.
Нам нужно выполнить инициализацию проекта (Initialization), а для этого в Gradle есть специальный плагин: ”
” (Init – сокращение от Initialization, легко запомнить).
Чтобы воспользоваться этим плагином, выполним в командной строке команду:
gradle init --type java-application
После успешного выполнения у нас появится Java проект.
Откроем теперь на редактирование билд скрипт нашего проекта. Билд скрипт — это файл с названием
build.gradle
, который описывает нюансы сборки (билда) приложения. Отсюда и название такое, билд скрипт. Можно сказать, что это скрипт сборки проекта.
Gradle — это такой универсальный инструмент, базовые возможности которого расширяются благодаря плагинам. Поэтому, в первую очередь обратим внимание на блок “plugins” (плагины):
plugins {
id 'java'
id 'application'
}
По умолчанию Gradle, в соответствии с указанным нами ”
--type java-application
“, выставил набор некоторых базовых плагинов (core plugins), то есть тех плагинов, которые входят в поставку самого Gradle. Если перейти на сайте
в раздел “Docs” (то есть документация), то слева в списке тем в разделе “Reference” мы видим раздел ”
“, т.е. раздел с описанием этих самых базовых плагинов. Давайте выберем именно те плагины, которые нам нужны, а не те, которые нам сгенерировал Gradle.
Согласно документации, ”
” обеспечивает базовые операции с Java кодом, такие как компиляция исходного кода. Так же, согласно документации, ”
” обеспечивает нас средствами для работы с “executable JVM application”, т.е. с java приложением, которые можно запустить как самостоятельное приложение (например, консольное приложение или приложение с собственным UI). Получается, что плагин “application” нам не нужен, т.к. нам не нужно самостоятельное приложение, нам нужно веб-приложение. Удалим его. А так же настройку “mainClassName”, которая известна только этому плагину.
Далее, в том же разделе “
“, где была приведена ссылка на документацию по Application Plugin, есть ссылка на Gradle War Plugin.
, как сказано в документации, предоставляет поддержку создания Java веб-приложений в формате war. В формате WAR означает, что вместо JAR архива будет создан WAR архив. Кажется, это то, что нам нужно. Кроме того, как сказано в документации, “The War plugin extends the Java plugin”. То есть мы можем заменить плагин java на плагин war. Следовательно, наш блок плагинов в итоге будет иметь следующий вид:
plugins {
id 'war'
}
Так же в документации к “Gradle War Plugin” сказано, что плагин использует дополнительный “Project Layout”. Layout с английского переводится как расположение. То есть war plugin по умолчанию рассчитывает на существование некоторого расположение файлов, которые он будет использовать для своих задач. Использовать для хранения файлов веб-приложения он будет следующий каталог:
src/main/webapp
Поведения плагина описано так:
То есть плагин будет учитывать файлы из этого расположения при сборке WAR архива нашего веб-приложения.
Кроме того, в документации Gradle War Plugin’а сказано, что данный каталог будет “root of the archive”. И уже в нём мы можем создать каталог WEB-INF и добавить туда файл web.xml. Что это за файл такой?
web.xml
— это “Deployment Descriptor” или “описатель развёртывания”. Это такой файл, который описывает, как нужно настроить наше веб-приложение для работы. В этом файле указывается, какие запросы будет обрабатывать наше приложение, настройки безопасности и многое другое. По своей сути он чем-то похож на manifest файл из JAR файла (см. “
“). Manifest файл рассказывает, как работать с Java Application (т.е. с JAR архивом), а web.xml рассказывает, как работать с Java Web Application (т.е. с WAR архивом).
Само понятие “Deployment Descriptor” возникло не само по себе, а описано в документе “
“.
Любое Java веб-приложение зависит от этого “Servlet API”. Важно понимать, что это API — то есть это описание некоторого контракта взаимодействия. Веб-приложения — это не самостоятельные приложения. Они запускаются на веб-сервере, который обеспечивает сетевое взаимодействие с пользователями.
То есть веб-сервер это некоторый “контейнер” для веб-приложений. Это логично, т.к. мы хотим писать логику веб-приложения, т.е. какие странички увидит пользователь и как они должны реагировать на действия пользователя. И мы не хотим писать код того, как будет отправляться сообщение пользователю, как будут передаваться байты информации и другие низкоуровневые и очень требовательные к качеству реализации вещи.
Кроме того, получается, что веб-приложения все разные, а передача данных одинакова. То есть миллиону программистов пришлось бы писать для одной и той же цели код снова и снова. Поэтому за часть взаимодействия с пользователем и за обмен данными отвечает веб-сервер, а за формирование этих данных отвечает веб-приложение и разработчик.
А чтобы связать эти две части, т.е. веб-сервер и веб-приложение, нужен контракт их взаимодействия, т.е. по каким правилам они это будут делать. Чтобы как-то описать контракт, как должно выглядеть взаимодействие между веб-приложением и веб-сервером и придуман Servlet API.
Интересно, что даже если вы используете фрэймворки вроде Spring, то “под капотом” всё равно работает Servlet API. То есть вы используете Spring, а Spring за Вас работает с Servlet API.
Получается, что наш проект веб-приложения должен зависеть (depends on)
от Servlet API. В этом случае Servlet API будет зависимостью (dependency). Как мы знаем, Gradle в том числе позволяет декларативным образом описывать зависимости проекта. А то, каким образом можно управлять зависимостями, описывают плагины.
Например, Java Gradle Plugin вводит способ управления зависимостями “testImplementation”, который говорит, что такая зависимость нужна только для тестов.
А вот Gradle War Plugin добавляет способ управления зависимостями “providedCompile”, который говорит, что такая зависимость не будет включена в WAR архив нашего веб-приложения.
Почему мы не включаем Servlet API в наш WAR архив? Потому что Servlet API будет предоставлен нашему веб-приложению самим веб-сервером.
providedCompile
.
Таким образом, блок зависимостей (dependencies) будет иметь следующий вид:
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testImplementation 'junit:junit:4.12'
}
Итак, вернёмся к web.xml файлу. По умолчанию, Gradle не создаёт никакой Deployment Descriptor, поэтому нам нужно сделать это самостоятельно.
Создадим каталог
src/main/webapp/WEB-INF
, а в нём создадим XML файл с названием
web.xml
.
Теперь давайте откроем саму спецификацию “Java Servlet Specification” и главу ”
“.
Как сказано в “14.3 Deployment Descriptor”, XML документ Deployment Descriptor’а описан схемой
Запуск
Пробуем запустить проект.
Видим, что новый пользователь появился в базе данных. Пароль зашифрован.
Заметки сохранились в базу данных.
Видим, что проект успешно запускается и работает. Для полного счастья нам не хватает только возможности входа через социальные сети. Что ж, приступим!
Кастомный вход пользователя
Самостоятельно написанный
AuthProvider
позволит пользователю входить не только по email, но и по имени пользователя.
Контроллеры
В нашем сервисе заметок будут следующие страницы:
- Главная;
- Регистрация;
- Вход;
- Список заметок пользователя.
К списку заметок должен быть доступ только у авторизованного пользователя. Остальные страницы являются общедоступными.
Создадим пакет
controllers
, в нем класс
IndexController
, содержащий обычный get-mapping главной страницы.
Класс
RegistrationController
отвечает за регистрацию пользователя. Post-mapping принимает данные из формы, сохраняет пользователя в базу данных и переадресовывает на страницу входа.
PasswordEncoder
будет описан позднее. Он используется для шифрования паролей.
Конфигурация фильтра и application.properties
Внедрим инъекцию OAuth2ClientContext, чтобы использовать ее в нашей конфигурации безопасности.
@Autowired
private OAuth2ClientContext oAuth2ClientContext;
OAuth2ClientContext используется при создании фильтра, который проверяет пользовательский запрос на вход через соцсеть. Фильтр доступен благодаря аннотации @EnableOAuth2Client. Все, что нам нужно, вызвать его в правильном порядке,
до
основного фильтра Spring Security. Только в таком случае мы сможем поймать перенаправления в процессе входа с OAuth2. Для этого используем
FilterRegistrationBean
, в котором выставляем приоритет нашего фильтра на -100.
Настройка oauth2 на примере google в spring security
. Для поддержки OAuth2 добавим в pom.xml следующую библиотеку:
Модифицируем нашу конфигурацию Spring Security в классе
SecurityConfig
Настройка spring security для классического входа
Spring Security помогает нам защищать приложение и его ресурсы от несанкционированного доступа. Мы создадим лаконичную рабочую конфигурацию в классе
SecurityConfig
, унаследованный от
WebSecurityConfigurerAdapter
, который поместим в пакет
config
. Пометим его аннотацией @EnableWebSecurity, которая включит поддержку Spring Security, и аннотацией @Configuration, которая указывает, что этот класс содержит некую конфигурацию.
Примечание:
в автоматически сконфигурированном pom.xml стояла версия родительского компонента Spring Boot 2.1.4.RELEASE, что мешало внедрить Security устоявшимся способом. Во избежание конфликтов в проекте рекомендуется изменить версию на 2.0.1.RELEASE.
Репозитории
Для сохранения сущностей в базу данных нам понадобятся репозитории, которые сделают всю грязную работу за нас. Создадим пакет
repos
, в нем создадим интерфейсы
Создание проекта
Идем на
и формируем основу проекта:
- Web — запуск приложения на встроенном Tomcat, url-сопоставления и тому подобное;
- JPA — связь с базой данных;
- Mustache — шаблонизатор, используется для генерации веб-страниц;
- Security — защита приложения. То, ради чего эта статья и создавалась.
Скачиваем получившийся архив и распаковываем в нужной вам папке. Запускаем его в IDE.
Вы можете выбирать БД на свое усмотрение. В качестве базы данных для проекта я использую MySQL, поэтому в файл pom.xml в блок добавляю следующую зависимость:
Конфигурация application.properties на данный момент следующая:
Страницы
Для создания страниц я использую
. Вы можете внедрить и другой, не принципиально. Для мета-информации, которая используется на всех страницах, создан файл meta.mustache. В нем же подключен Bootstrap, чтобы страницы нашего проекта выглядели симпатичней. Страницы создаются в директории “src/main/resources/templates”. Файлы имеют расширение mustache. Размещение html-кода непосредственно в статье сделает ее слишком большой, поэтому вот
Сущности
Создадим пакет
entities
, в который поместим сущности базы данных. Пользователь будет описываться классом
Усовершенствуем контроллер
Мы настроили Spring Security. Самое время воспользоваться этим в контроллере заметок. Теперь каждый mapping будет принимать дополнительный параметр Principal, по которому постарается найти пользователя. Почему нельзя напрямую внедрить класс
Итоговый запуск проекта
После небольших косметических изменений и добавления кнопки выхода, проводим итоговый запуск проекта.
Пользователь успешно входит и через обычную форму, и через Google аккаунт. Этого мы и добивались!
Надеюсь, эта статья прояснила определенные моменты в создании веб-приложения, его защите с помощью Spring Security и комбинировании разных способов входа. C полным кодом проекта вы можете