- •Цель работы
- •Теоретическая часть
- •Основные элементы Spring Security
- •Что произойдет после подключения Spring Security
- •Ход выполнения работы
- •Часть 0. Подготовка
- •Часть 1. Подключение Spring Security
- •Часть 2. Расширение модели пользователя
- •2.1. Создайте перечисление роли пользователя
- •2.2. Обновите сущность User
- •2.3. Обновите UserDto
- •Часть 3. Подготовка репозитория и загрузки пользователя
- •3.1. Обновите UserRepository
- •3.2. Создайте класс CustomUserDetails
- •3.3. Создайте класс CustomUserDetailsService
- •3.4. Почему это удобнее, чем возвращать встроенный User
- •Часть 4. Шифрование паролей и конфигурация безопасности
- •4.1. Создайте конфигурационный класс SecurityConfig
- •4.2. Что делает эта конфигурация
- •Часть 5. Регистрация пользователя
- •5.1. Создайте AuthService
- •5.2. Создайте AuthController
- •5.3. Проверьте регистрацию
- •Часть 6. Базовая аутентификация через HTTP Basic
- •6.1. Как это работает
- •6.2. Проверьте закрытые URL
- •Часть 7. Авторизация по ролям
- •7.1. Ограничение URL по ролям
- •7.2. Пример контроллера администратора
- •7.3. Методная авторизация
- •Часть 8. Как работают фильтры Spring Security
- •Часть 9. Сессии в Spring Security
- •9.1. Как работает сессия
- •9.2. Особенности сессий
- •9.3. Как это связано с нашим приложением
- •Часть 10. JWT: stateless-подход
- •10.1. Идея JWT
- •10.3. Логин с выдачей JWT
- •10.4. JWT-фильтр
- •10.5. Конфигурация для JWT
- •Часть 11. Сравнение сессий и JWT
- •Сессии
- •Часть 12. Проверка работы приложения
- •12.1. Проверка регистрации
- •12.2. Проверка Basic Auth
- •12.3. Проверка ролей
- •12.4. Проверка JWT
- •Самостоятельные задания
- •Контрольные вопросы
•сервер сам хранит информацию о пользователе;
•нет необходимости пересчитывать токен на каждом запросе.
Минусы:
•сервер хранит состояние;
•сложнее масштабировать приложение без общей сессии;
•хуже подходит для REST API и мобильных клиентов.
9.3.Как это связано с нашим приложением
Если бы мы включили formLogin() , Spring Security создал бы стандартную страницу логина и после успешного входа сохранял бы пользователя в сессии через JSESSIONID . В лабораторной мы используем httpBasic() как простой старт, но логика SecurityContext и session-based хранения состояния важна для понимания архитектуры.
Часть 10. JWT: stateless-подход
В REST API часто используют JWT вместо сессии. В этом случае сервер не хранит состояние пользователя между запросами.
10.1.Идея JWT
1.Пользователь отправляет логин и пароль на /auth/login .
2.Сервер проверяет их и создает токен JWT.
3.Клиент сохраняет токен.
4.При каждом следующем запросе клиент отправляет заголовок:
Authorization: Bearer <jwt>
1.Сервер проверяет токен и заново создает объект Authentication для текущего запроса.
10.2.Пример JwtService
package org.example.security;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets; import java.util.Date;
@Service
public class JwtService {
private final String secret = "verySecretKeyForJwtTokenVerySecretKey12345";
15
public String generateToken(String username) { SecretKey key =
Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
return Jwts.builder()
.subject(username)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 1000 * 60
* 60))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
}
10.3. Логин с выдачей JWT
package org.example.controller;
import jakarta.validation.Valid; import lombok.RequiredArgsConstructor;
import org.example.model.dto.LoginRequest; import org.example.security.JwtService;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.bind.annotation.*;
@RestController @RequestMapping("/auth") @RequiredArgsConstructor
public class JwtAuthController {
private final AuthenticationManager authenticationManager; private final JwtService jwtService;
@PostMapping("/login")
public String login(@RequestBody @Valid LoginRequest request) { authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken( request.getEmail(), request.getPassword()
)
);
return jwtService.generateToken(request.getEmail());
}
}
Теперь после логина пользователь получает токен.
16
10.4. JWT-фильтр
Чтобы сервер умел читать JWT из заголовка, нужен фильтр:
package org.example.security;
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws
ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,
17
