18.05.2021
Последняя модификация:25.08.2021
Здесь я опишу:
- Как сделать заготовку для Maven SpringBoot проекта и импортировать его в Eclipse.
- Как сменить порт встроенного Tomcat.
- Добавить к проекту Thymeleaf.
- Добавить basic аутентификацию к проекту.
- Добавить form аутентификацию к проекту.
- Настройка формы аутентификации.
- Сделать доступ только по https.
Создание SpringBoot проекта.
SpringBoot позволяет развёртывать и запускать программу со встроенным сервером приложений как обычную Java-программу, указав для исполнения просто JAR-файл.
Используя Eclipse и Maven это делается так:
1. На странице http://start.spring.io/ создаём набор файлов, которые представляют собой Maven-проект:

Здесь надо:
- выбрать "Maven project" и Java, Packaging:Jar
- указать нужные Group, Artifact
- добавить зависимости "WEB" и "DEVELOPER TOOLS"
2. Нажать GENERATE и получить файл example.zip
3. Раскрыть example.zip
4. В Eclipse: Import - Existing Maven Projects, указать каталог, куда раскрыт example.zip, импортировать проект.
5. В проекте добавить пакет ru.arccomm.springboot.example.controller
6. Создать новый контроллер HelloWorldController:
package ru.arccomm.springboot.example.controller;
import ...
@RestController
public class HelloWorldController {
@RequestMapping("/helloworld")
String helloworld() {
return "Hello, world!";
}
}
7. Запустить приложение в Eclipse как Java application.
8. В браузере в строке адреса указать:
localhost:8080/helloworld
В ответе от сервера получаем:
Hello, world!
Ладно, SpringBoot со встроенным Tomcat-ом из Eclipse запускается.
9. В окне терминала перейду в каталог example с моим Maven-проектом и дам команду:
mvn clean install
в результате в подкаталоге target получаю файл example-0.0.1-SNAPSHOT.jar
10. Это уже законченное приложение, которое можно запустить из командной строки:
java -jar example-0.0.1-SNAPSHOT.jar
Если обратиться из браузера к ранее указанному адресу, получим тот же ответ.
Т.е. мы получили WEB-приложение со встроенным Tomcat-сервером внутри одного JAR-файла.
Смена порта Tomcat.
По умолчанию Tomcat использует порт 8080. Указать другой можно в файле src/main/resources/application.properties:
server.port=8180
Добавляю Thymeleaf к проекту.
Thymeleaf используется для динамического изменения контента при отдаче клиенту HTML-страницы.
Подключение Thymeleaf в проект:
1. В POM.xml вставляется зависимость:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Этого достаточно, чтобы все классы Thymeleaf были инициализированы в приложении.
2. В контроллере меняется аннотация RestController на Controller:
package ru.arccomm.springboot.example.controller;
import ...
@Controller
public class HelloWorldController {
@RequestMapping("/th")
String th() {
return "hello";
}
}
return "hello" указывает, что перед возвратом клиенту HTML-страницы, файл
src/main/resources/templates/hello.html будет обработан Thymeleaf.
3. Файлы шаблонов Thymeleaf создаются в каталоге src/main/resources/templates. Для этого примера я создал hello.html:
<!DOCTYPE html> <html lang="ru" xmlns:th="http://thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Это пример</title> </head> <body> <p>Thymeleaf hello</p> </body> </html>
Thymeleaf практически ничего не будет заменять в этом шаблоне, поскольку это чистый HTML-код.
4. Обращение из браузера к localhost:8080/hello даст:
Thymeleaf hello
Базовая аутентификация
Делаю доступ к страницам проекта через login/password.
В ресурсах у меня теперь есть:
templates/hello.html
templates/open/open.html
templates/adm/adm.html
Для каждого из этих ресурсов я сделаю отличающиеся права доступа.
1. В pom.xml добавляю зависимость:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2. Создаю новый пакет ru.arccomm.springboot.example.config и в нём класс SecurityConfig который унаследован от WebSecurityConfigurerAdapter :
package ru.arccomm.springboot.example.config;
import ...
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
// login/passw хранятся в памяти
// Здесь заданы login,passw,role для каждого пользователя системы
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("normaluser").password(passwordEncoder().encode("123456")).roles("USERS")
.and()
.withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN")
;
}
@Override
// Указываю, что доступ ко всем страницам должен проходить только после аутентификации
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/adm/**").hasRole("ADMIN") // /adm/** только для пользователей группы "ADMIN"
.antMatchers("/open/**").permitAll() // /open/** разрешено всем без входа в систему
.anyRequest().authenticated() // остальные страницы только после входа в систему
.and()
.httpBasic(); // использую Basic аутентификацию
;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- Класс обозначен аннотациями @Configuration и @EnableWebSecurity. Spring будет использовать его при старте.
- В config() для мененеджера аутентификации данные двух пользователей "normaluser" и "admin" хранятся в памяти. При создании записей о пользователях и паролях используется шифрование Bcrypt. У этих пользователей заданы разные роли.
- В config() для http указаны права доступа к конкретным группам ресурсов. Здесь важна последовательность вызова разрешений. Например, к ресурсу "/open/*" можно обращаться любому пользователи, а следом идёт требование аутентификации для всех остальных ресурсов. Последовательность указания прав доступа важна.
3. Описание маппирования ресурсов:
package ru.arccomm.springboot.example.controller;
import ...
@Controller
public class HelloWorldController {
@RequestMapping("/hello")
String hello() {
return "hello";
}
@RequestMapping("/adm/adm")
String adm() {
return "adm/adm";
}
@RequestMapping("/open/open")
String open() {
return "open/open";
}
}
4. Доступ к ресурсам из браузера:
- /open/open будет доступен всем пользователям без аутентификации
- /hello потребует аутентификации. Можно аутентифицироваться как "normaluser", так и "admin".
- /adm/adm разрешит доступ только "admin" после успешной аутентификации.
Сами HTML-файлы я здесь не привожу, чтобы не раздувать описание.
5. Basic authentication не очень-то предполагает logout. Браузеры будут упрямо посылать заголовки и cookie с данными аутентификации.
Полноценный вход/выход пользователя в приложении нужно реализовывать через FORM authentication.
Добавить FORM аутентификацию к проекту.
Basic authentication не позволяет по простому выйти пользователю из приложения. Простейшая Form authentication делается в классе SecurityConfig небольшой правкой:
@Override
// Указываю, что доступ ко всем страницам должен проходить только после аутентификации
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/adm/*").hasRole("ADMIN")
.antMatchers("/open/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin() // использую страндартную форму Spring для login/password
.permitAll()
;
}
Больше ничего не исправляю.
При обращении к любой защищённой странице получаю приглашение ввести имя и пароль:
После успешной аутентификации попадаю на ту страницу, которая была в запросе.
Чтобы выйти из приложения достаточно в строке адреса указать страницу localhost:8180/logout
Настройка формы аутентификации
Стандартная форма для входа в систему нас не всегда удоволетворяет. Делаю свою:
1. Класс SecurityConfig теперь выглядит так:
package ru.arccomm.springboot.example.config;
import ...
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
// login/passw хранятся в памяти
// Здесь заданы login,passw,role для каждого пользователя системы
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("normaluser").password(passwordEncoder().encode("123456")).roles("USERS")
.and()
.withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN")
;
}
@Override
// Указываю, что доступ ко всем страницам должен проходить только после аутентификации
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/adm/*").hasRole("ADMIN")
.antMatchers("/open/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") // url для запроса login
.failureUrl("/login?error=true") // url, куда отправиться при ошибке аутентификации
.defaultSuccessUrl("/hello") // страница по умолчанию, если пользователь просто обратился к странице "/login"
.permitAll()
.and()
.logout()
.logoutUrl("/logout") // url для выхода из системы
.logoutSuccessUrl("/logoutDone") // куда отправиться после успешного выхода из системы
.permitAll()
;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Новые методы я прокомментировал в коде. Здесь заданы параметры для login и для logout.
2. Класс HelloWorldController теперь выглядит так:
package ru.arccomm.springboot.example.controller;
import ...
@Controller
public class HelloWorldController {
@RequestMapping("/hello")
String th() {
return "hello";
}
@RequestMapping("/adm/adm")
String adm() {
return "adm/adm";
}
@RequestMapping("/open/open")
String open() {
return "open/open";
}
// Login form
@RequestMapping("/login")
public String login() {
return "auth/loginForm";
}
//Successfull logout page
@RequestMapping("/logoutDone")
public String logoutDone() {
return "auth/logoutDone";
}
}
Здесь добавлено маппирование "/login" и "/logoutDone".
3. В каталоге src/main/resources/templates добавляю подкаталог auth, в котором будут все HTML-файлы, касающиеся аутентификации.
Файл templates/auth/loginForm.html:
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Login form</title>
</head>
<body>
<h2>Login form</h2>
<p style="color:red" th:if="${param.error}">Error</p>
<form th:action="@{/login}" method="post">
<label for="username">Login: </label>
<input type="text" id="username" name="username" autofocus="autofocus" />
<br><label for="password">Passw: </label>
<input type="password" id="password" name="password" />
<br><input id="submit" type="submit" value="Log in" />
</form>
</body>
</html>
При ошибке аутентификации будет вызвана страница "/login?error=true". Spring передаст в форму саму ошибку. Текст ошибки высветится красным. Обычно это "Invalid username and password".
Файл templates/auth/logoutDone.html:
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Logout done</title>
</head>
<body>
<h2>Logout done</h2>
</body>
</html>
HTML/CSS-красоту я здесь не привожу. Это уже на вкус и цвет разработчиков.
Сделать доступ только по https.
Сегодня аутентифицироваться в приложении по открытому http-протоколу гарантированно небезопасно. Поэтому добавлю в проект доступ к нему только через защищённый HTTPS.
1. Надо создать самоподписанный сертификат для приложения.
Вот командная строка в Linux:
keytool -genkey -alias tomcat -keyalg RSA -keysize 2048 -keystore keystore.file -validity 3650
- Пароль ввожу changeit
- first and last name ввожу localhost
В домашнем каталоге пользователя будет создан keystore.file
2. Созданный keystore.file из домашнего каталога пользователя надо скопировать в каталог src/main/resources приложения.
3. В файле src/main/resources/application.properties добавляю и изменяю:
server.port=8543 server.ssl.key-store-type=JKS server.ssl.key-store=classpath:keystore.file server.ssl.key-store-password=changeit server.ssl.key-alias=tomcat security.require-ssl=true
Порт заменил.
Формат хранилища ключей JKS.
Указал, где брать файл хранилища - keystore.file
Указал пароль.
Имя сертификата - tomcat, посколку он нужен именно Tomcat-у, встроенному в наше приложение.
Указал, что при соединеннии требуется использовать именно SSL-коннектор для Tomcat-а.
4. Теперь подключаться надо по URL:
5. Браузер будет рычать на самоподписанный сертификат, но защищённый канал работать будет.
Чтобы пользователям не приходилось проходить процедуру согласия на самоподписанный сертификат, стоит получить его для вашего сайта на Letsencrypt.