JavaRush /Курси /Модуль 5. Spring /Коротко про попередні лекції

Коротко про попередні лекції

Модуль 5. Spring
Рівень 18 , Лекція 4
Відкрита

Сьогодні займемося практичним впровадженням JWT у наш додаток. Додамо генерацію й валідацію токенів, налаштуємо фільтри безпеки та забезпечимо захист REST API. Для цього створимо невеликий Spring Boot додаток, де авторизація відбуватиметься через JWT.

JWT — це не просто модне слово. Це зручний, легкий і широко вживаний механізм аутентифікації в сучасних веб-додатках. Наприклад, якщо ти розробник і йдеш на співбесіду, будь певен: запитають «розкажи про JWT». А якщо ні (пощастило!), то в реальному житті ти з ним зіткнешся на будь-якому проєкті, пов'язаному з аутентифікацією.


План нашого додатку

Наш додаток складатиметься з двох основних функціональностей:

  1. Аутентифікація — користувач надсилає свої облікові дані (логін і пароль), а сервер повертає JWT.
  2. Доступ до захищених ресурсів — користувач передає JWT у заголовку запитів, і якщо токен валідний, сервер надає доступ до ресурсу.

Крок 1. Підготовка: конфігурація проєкту

Спочатку створимо Spring Boot проєкт. Ми будемо використовувати:

  • Maven для керування залежностями.
  • Spring Boot версії 2.7+.
  • Залежності: spring-boot-starter-security, spring-boot-starter-web, jjwt (Java JWT бібліотека).

Конфігурація Maven Створи проєкт за допомогою Spring Initializr (https://start.spring.io/). Додай такі залежності у pom.xml:


<dependencies>
  <!-- Spring Boot Starter Web -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- Spring Boot Starter Security -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

  <!-- Java JWT бібліотека -->
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
  </dependency>
</dependencies>

Крок 2. Налаштування SecurityConfig

Потрібно налаштувати безпеку й прибрати стандартну форму логіну Spring Security. Замiнимо її авторизацією через JWT.

Створи клас SecurityConfig:


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll() // Доступ до цих маршрутів без авторизації
            .anyRequest().authenticated()          // Всі інші маршрути потребують авторизації
            .and()
            .httpBasic();

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Ми вимкнули CSRF (лише для простого навчального прикладу), налаштували доступ до ендпоінтів /api/auth/** без авторизації і вимагатимемо авторизацію для всіх інших запитів.


Крок 3. Створення сутності користувача

Потрібно створити сутність користувача, щоб змоделювати процес аутентифікації. Для простоти зберігатимемо користувачів в пам'яті.


public class User {
    private String username;
    private String password;

    // Конструктори, геттери і сеттери
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}

Крок 4. Створення сервісу для генерації JWT

Тепер реалізуємо сервіс, який генеруватиме JWT.


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class JwtService {

    private final String SECRET_KEY = "your-256-bit-secret"; // Ніколи не зберігаємо секрети в коді в реальних проєктах

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) // 30 хвилин
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
}

Крок 5. Контролер для аутентифікації

Створимо контролер, який оброблятиме запити для аутентифікації.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private JwtService jwtService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping("/login")
    public String login(@RequestBody User loginRequest) {
        // Приклад: статичний користувач для тестів
        User user = new User("admin", passwordEncoder.encode("password"));

        if (user.getUsername().equals(loginRequest.getUsername()) &&
                passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
            return jwtService.generateToken(user.getUsername());
        }
        throw new RuntimeException("Невірні облікові дані");
    }
}

Цей ендпоінт приймає username і password, порівнює їх із даними користувача (в даному випадку закодованими в пам'яті), і якщо все валідно, повертає JWT.


Крок 6. Валідація JWT

Тепер додамо фільтр, який оброблятиме кожен запит, перевіряючи переданий токен.


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final String SECRET_KEY = "your-256-bit-secret";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7); // Видаляємо "Bearer "

            try {
                Claims claims = Jwts.parser()
                        .setSigningKey(SECRET_KEY)
                        .parseClaimsJws(token)
                        .getBody();

                String username = claims.getSubject();

                // Встановлюємо автентифікацію
                UsernamePasswordAuthenticationToken auth =
                        new UsernamePasswordAuthenticationToken(username, null, null);
                SecurityContextHolder.getContext().setAuthentication(auth);
            } catch (Exception e) {
                System.out.println("Недійсний токен.");
            }
        }

        filterChain.doFilter(request, response);
    }
}

Не забудь зареєструвати цей фільтр у конфігурації.


@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/api/auth/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

Крок 7. Тестування

Тепер можна протестувати додаток:

  1. Надішли POST запит на /api/auth/login з JSON:
    
    {
     "username": "admin",
     "password": "password"
    }
    

    Отримаєш JWT у відповіді.

  2. Використай цей JWT у заголовку Authorization для будь-якого захищеного маршруту:
    
    Authorization: Bearer <твій-токен>
    

Ось і все! Ми захистили наш REST API за допомогою JWT. У реальному житті тобі знадобиться додати багато покращень: зберігання користувачів у базі даних, обробка Refresh токенів та багато іншого. Але тепер ти знаєш основні принципи.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ