JavaRush /Курсы /Модуль 5. Spring /Краткое содержание предыдущих лекций

Краткое содержание предыдущих лекций

Модуль 5. Spring
11 уровень , 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. Заменим её на авторизацию через 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 токенов и многое другое. Но теперь вы знаете основные принципы.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ