Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
GitLab
Напишем простой сервис аутентификации с выдачей JWToken. Для реализации будем использовать Java 17, SpringBoot 3.2.0, h2, Maven в памяти.
Cоздадим и настроим проект https://start.spring.io/
Нам понадобится:
Web
Security
JPA
H2 Database
Lombok
Настроим подключение к нашей БД которая будет находится в памяти, так же сервер будем запускать на порту 9090
Для автоматического заполнения БД создадим пару файлов с созданием и заполнением таблиц data.sql и schema.sql.
Содержание файлов data && schema
Далее нам потребуется создать сопутствующие сущности User, Role
Закончили с базовой настройкой, переходим к основному классу WebConfiguration настройки Security. В нем мы должны настроить bean SecurityFilterChain, так же создадим bean PasswordEncoder для возможности шифрования пароля пользователя.
Для шифрование пароля будем использовать BCrypt.
Далее рассмотрим SecurityFilterChain:
http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable);
В этой строчке мы отключаем CSRF && CORS так как для простого фунционала нам они не понадобятся.
В проде обязательно использовать CSRF && CORS
Т.к h2-console использует frame то для корректной работы нужно добавить header x-frame-options "SAMEORIGIN" или отключить его FrameOptionsConfig::disable
X-Frame-Options используется для предотвращения кликджекинга на сайте. Он определяет, разрешено ли браузеру отображать страницу в <frame>, <iframe>, <embed> или <object>
https://www.geeksforgeeks.org/http-headers-x-frame-options/
headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
OR
headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
Далее разрешим доступ к консоли h2 для всех пользователей и для остальных запросов требуется аутентификация
http.authorizeHttpRequests(authz ->
authz.requestMatchers("/h2-console/**").permitAll().anyRequest().authenticated());
Главное действие будет происходить в фильтрации запроса
http.addFilterAt(initialAuthenticationFilter, BasicAuthenticationFilter.class);
Здесь мы добавляем свой фильтр в цепочку фильтрации initialAuthenticationFilter который мы inject'им в методе
public SecurityFilterChain securityFilterChain(HttpSecurity http, InitialAuthenticationFilter initialAuthenticationFilter)
Данный класс будет расширять OncePerRequestFilter и переопределять два метода
doFilterInternal
shouldNotFilter
Данный фильтр проверяет header Authorization, если он пустой то проверяет передано ли в теле запроса JSON с именем и паролем для аутентификации, проверят пользователя и если все ок выдает JWToken.
Рассмотрим более подробно данный класс.
Для формирования JWToken и проверки пользователя создадим и заинжектим два класса
private final JwtService jwtService;
private final UsernamePasswordAuthenticationProvider authenticationProvider;
Для работы с JWT потребуются следующие библиотеки
Здесь мы будем генерировать ключ подписи и собственно сам JWT.
Keys.hmacShaKeyFor(signingKey.getBytes(StandardCharsets.UTF_8));
шифруем ключ BASE64
В методе generatedJwt строим JWT.
Задаем поля в payload и нагрузку, устанавливаем дату окончания действия и ключ подписи
payload {
"role"
"user_id
"username"
"exp"
"sub"
}
Данный класс проверяет наличие пользователя и корректность пароля и возвращает аутентификацию.
Класс UserDetailsService возвращает пользователя если он имеется в базе
Вернемся к классу InitialAuthenticationFilter.
Метод doFilterInternal
из request мы извлекаем JSON с логином и паролем
проеряем пользователя
Authentication authentication = new UsernamePasswordAuthentication(username, password); authentication = authenticationProvider.authenticate(authentication);если все ок выдаем JWT в response в заголовок Authorization: Bearer *****
String jwt = jwtService.generatedJwt(authentication); response.setHeader("Authorization", HeaderValues.BEARER + jwt);
Метод shouldNotFilter
Позволяет применить фильтр к отпределенному/ым uri
В данном случае применится к запросам на uri /login
Проверка
curl -v -d '{"username":"admin", "password":"123"}' -H "Content-Type: application/json" -X POST http://localhost:9090/login
Видим, что появился header
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwidXNlcl9pZCI6IjEiLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzAzMTU1NzAxLCJzdWIiOiJhZG1pbiJ9.aNHtaBa-7WDXO_MMl83MG9wxTO0MnMmEwdjgzSOrh0g
Данный пример демонстрирует как достаточно быстро и просто поднять сервис аутентификации и выдачи JWT. Так же можно добавить проверку токена, выдача refresh token и запрос со стороннего сервиса, но это уже другая история.