Авторизация и аутентификация в Java: Spring Security и JWT
Организация безопасного доступа к ресурсам — одна из ключевых задач при разработке веб-приложений на Java. В этой статье мы разберём два фундаментальных понятия: аутентификацию (проверку подлинности пользователя) и авторизацию (проверку прав доступа). Рассмотрим практические примеры с использованием популярного фреймворка Spring Security и технологии JSON Web Tokens (JWT).
1. Основные понятия
Прежде чем перейти к коду, важно чётко разграничить термины:
- Аутентификация (Authentication) — процесс проверки, кем является пользователь. Обычно выполняется через логин и пароль, биометрию или токены.
- Авторизация (Authorization) — процесс определения, что пользователю разрешено делать. Например, доступ к определённым эндпоинтам API или функциям приложения.
В современных Java-приложениях для реализации этих механизмов чаще всего используют связку Spring Security + JWT. JWT позволяет хранить информацию о пользователе в компактном токене, который легко передавать между клиентом и сервером.
2. Настройка Spring Security с JWT
Добавим необходимые зависимости в pom.xml (Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
3. Создание сервиса для работы с JWT
Создадим класс JwtService, который будет отвечать за генерацию и проверку токенов:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class JwtService {
private final SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private final long expirationMs = 86400000; // 24 часа
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(secretKey)
.compact();
}
public String extractUsername(String token) {
return extractAllClaims(token).getSubject();
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean isTokenValid(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
return extractAllClaims(token).getExpiration().before(new Date());
}
}
4. Фильтр аутентификации
Теперь реализуем фильтр, который перехватывает HTTP-запросы и проверяет JWT-токен:
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public JwtAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext