Ссылка на проект: GitLab

Напишем простой сервис аутентификации с выдачей JWToken. Для реализации будем использовать Java 17, SpringBoot 3.2.0, h2, Maven в памяти.

Cоздадим и настроим проект https://start.spring.io/

SpringInitializer
SpringInitializer

Нам понадобится:

  • Web

  • Security

  • JPA

  • H2 Database

  • Lombok

Настроим подключение к нашей БД которая будет находится в памяти, так же сервер будем запускать на порту 9090

application.properties
application.properties

Для автоматического заполнения БД создадим пару файлов с созданием и заполнением таблиц data.sql и schema.sql.

Содержание файлов data && schema
data.sql
data.sql
schema.sql
schema.sql

Далее нам потребуется создать сопутствующие сущности User, Role

User
User
Role
Role

Закончили с базовой настройкой, переходим к основному классу WebConfiguration настройки Security. В нем мы должны настроить bean SecurityFilterChain, так же создадим bean PasswordEncoder для возможности шифрования пароля пользователя.

WebConfiguration.jav
WebConfiguration.jav

Для шифрование пароля будем использовать 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)

InitialAuthenticationFilter.java
InitialAuthenticationFilter.java

Данный класс будет расширять OncePerRequestFilter и переопределять два метода

  • doFilterInternal

  • shouldNotFilter

Данный фильтр проверяет header Authorization, если он пустой то проверяет передано ли в теле запроса JSON с именем и паролем для аутентификации, проверят пользователя и если все ок выдает JWToken.

Рассмотрим более подробно данный класс.

Для формирования JWToken и проверки пользователя создадим и заинжектим два класса

private final JwtService jwtService;
private final UsernamePasswordAuthenticationProvider authenticationProvider;

JwtService.java
JwtService.java

Для работы с JWT потребуются следующие библиотеки

pom.xml
pom.xml

Здесь мы будем генерировать ключ подписи и собственно сам JWT.

Keys.hmacShaKeyFor(signingKey.getBytes(StandardCharsets.UTF_8));

шифруем ключ BASE64

В методе generatedJwt строим JWT.

Задаем поля в payload и нагрузку, устанавливаем дату окончания действия и ключ подписи

payload {
"role"
"user_id
"username"
"exp"
"sub"
}

UsernamePasswordAuthenticationProvider.java
UsernamePasswordAuthenticationProvider.java

Данный класс проверяет наличие пользователя и корректность пароля и возвращает аутентификацию.

Класс UserDetailsService возвращает пользователя если он имеется в базе

UserService.java
UserService.java

Вернемся к классу 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

Содержание JWToken
Содержание JWToken

Данный пример демонстрирует как достаточно быстро и просто поднять сервис аутентификации и выдачи JWT. Так же можно добавить проверку токена, выдача refresh token и запрос со стороннего сервиса, но это уже другая история.

Комментарии (6)


  1. Belvarm
    17.12.2023 05:48

    Приложите пожалуйста ссылку на гит, а то с скринами не очень то удобно


    1. SilverRid Автор
      17.12.2023 05:48

      Ссылка в самом начале статьи, попробую выделить получше https://gitlab.com/SilverRid/SpringSecurityJWT


  1. Rockway
    17.12.2023 05:48

    Благодарю за статью, все просто и понятно, за ссылку на источник отдельное спасибо. Сегодня со статьи переписал весь код, интересно было потренироваться, понять некоторые моменты.


    1. SilverRid Автор
      17.12.2023 05:48

      Спасибо, по security, еще много чего интересного, буду выкладывать по возможности.


  1. Shiko_Siberia
    17.12.2023 05:48

    Жидкая статья.

    Напоминает пересказ этого видео

    https://www.youtube.com/watch?v=NIv9TFTSIlg


    1. SilverRid Автор
      17.12.2023 05:48

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